├── .gitignore ├── assets ├── logo.png └── serialized_main_page.txt ├── screenshot.png ├── Cargo.toml ├── src ├── main.rs ├── widget.rs ├── image.rs ├── button.rs ├── lib.rs ├── vector.rs ├── serialization.rs ├── color.rs ├── text.rs ├── render.rs ├── rect.rs └── engine.rs ├── examples ├── json_widget.rs └── example_app.rs ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.DS_Store 5 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaroslaw-weber/rust-ox-ui/HEAD/assets/logo.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaroslaw-weber/rust-ox-ui/HEAD/screenshot.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jaroslaw Weber"] 3 | name = "oxui" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | easer = "0.2.0" 8 | find_folder = "0.3.0" 9 | piston_window = "0.67.0" 10 | serde_derive = "1.0.10" 11 | serde_json = "1.0.2" 12 | serde = "1.0.10" -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate piston_window; 2 | extern crate find_folder; 3 | 4 | mod engine; 5 | mod widget; 6 | mod color; 7 | mod image; 8 | mod text; 9 | mod vector; 10 | mod rect; 11 | mod example_app; 12 | mod button; 13 | 14 | fn main() { 15 | //run example app 16 | example_app::run(); 17 | } -------------------------------------------------------------------------------- /src/widget.rs: -------------------------------------------------------------------------------- 1 | use rect::Rectangle; 2 | 3 | //widget. trait to build application/widgets 4 | pub trait Widget { 5 | fn build(&self) -> Rectangle; 6 | 7 | fn on_button_click(&mut self, button_id: i32); 8 | 9 | fn get_window_size(&self)->(f32,f32); 10 | 11 | fn get_window_name(&self)->String; 12 | } 13 | -------------------------------------------------------------------------------- /src/image.rs: -------------------------------------------------------------------------------- 1 | //image loading 2 | #[derive(Debug, Serialize, Deserialize)] 3 | pub struct Image { 4 | path: String, 5 | } 6 | impl Image { 7 | pub fn new(path: &str) -> Image { 8 | Image { path: path.to_string() } 9 | } 10 | pub fn get_path(&self) -> String { 11 | (&self.path).to_string() 12 | } 13 | } -------------------------------------------------------------------------------- /src/button.rs: -------------------------------------------------------------------------------- 1 | 2 | #[derive(Debug, Serialize, Deserialize)] 3 | pub struct Button { 4 | id: i32, 5 | } 6 | //button component, have id (usually unique) for listeners 7 | impl Button { 8 | pub fn get_id(&self) -> i32 { 9 | self.id 10 | } 11 | pub fn new(id: i32) -> Button { 12 | Button { id: id } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate piston_window; 2 | extern crate find_folder; 3 | extern crate serde; 4 | #[macro_use] 5 | extern crate serde_derive; 6 | extern crate serde_json; 7 | 8 | pub mod engine; 9 | pub mod widget; 10 | pub mod color; 11 | pub mod image; 12 | pub mod text; 13 | pub mod vector; 14 | pub mod rect; 15 | pub mod button; 16 | pub mod serialization; -------------------------------------------------------------------------------- /src/vector.rs: -------------------------------------------------------------------------------- 1 | 2 | //pair of numbers 3 | #[derive(Copy,Clone, Debug, Serialize, Deserialize)] 4 | pub struct Vector2 { 5 | x: f32, 6 | y: f32, 7 | } 8 | 9 | impl Vector2 { 10 | pub fn get_x(&self) -> f32 { 11 | self.x 12 | } 13 | pub fn get_y(&self) -> f32 { 14 | self.y 15 | } 16 | 17 | pub fn new(x: f32, y: f32) -> Vector2 { 18 | Vector2 { x: x, y: y } 19 | } 20 | 21 | pub fn zero() -> Vector2 { 22 | Vector2::new(0.0, 0.0) 23 | } 24 | 25 | pub fn one()->Vector2 26 | { 27 | Vector2::new(1.,1.) 28 | } 29 | 30 | pub fn half()->Vector2 31 | { 32 | Vector2::new(0.5,0.5) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/serialization.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::BufReader; 3 | use std::io::prelude::*; 4 | use serde_json; 5 | use rect::Rectangle; 6 | 7 | pub fn load_rectangle_from_file(path: &str) -> Rectangle { 8 | 9 | let file = File::open(path).unwrap(); 10 | let mut buf_reader = BufReader::new(file); 11 | let mut contents = String::new(); 12 | buf_reader.read_to_string(&mut contents).unwrap(); 13 | let as_rectangle: Rectangle = serde_json::from_str(&contents).unwrap(); 14 | as_rectangle 15 | } 16 | 17 | pub fn save_rectangle_to_json_file(rt: &Rectangle, path: &str) { 18 | let as_json = serde_json::to_string(rt).unwrap(); 19 | let mut buffer = File::create(path).unwrap(); 20 | buffer.write_all(&as_json.as_bytes()).unwrap(); 21 | } -------------------------------------------------------------------------------- /examples/json_widget.rs: -------------------------------------------------------------------------------- 1 | extern crate oxui; 2 | use oxui::rect::Rectangle; 3 | use oxui::widget::Widget; 4 | use oxui::engine; 5 | use oxui::serialization; 6 | 7 | struct JsonWidget {} 8 | 9 | //implementation of widget 10 | impl Widget for JsonWidget { 11 | fn build(&self) -> Rectangle { 12 | serialization::load_rectangle_from_file("assets/serialized_main_page.txt") 13 | } 14 | 15 | //on button click listeners 16 | fn on_button_click(&mut self, button_id: i32) {} 17 | 18 | fn get_window_size(&self) -> (f32, f32) { 19 | (500., 800.) 20 | } 21 | 22 | fn get_window_name(&self) -> String { 23 | "Loaded from json".to_string() 24 | } 25 | } 26 | 27 | fn main() { 28 | let mut w = JsonWidget {}; 29 | engine::initialize(&mut w); 30 | } -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | //color as struct, also contains helper methods 2 | #[derive(Copy,Clone, Debug, Serialize, Deserialize)] 3 | pub struct Color { 4 | color: (f32, f32, f32, f32), 5 | } 6 | 7 | impl Color { 8 | pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color { 9 | Color { color: (r, g, b, a) } 10 | } 11 | 12 | pub fn to_arr(&self) -> [f32; 4] { 13 | let (r, g, b, a) = self.color; 14 | [r, g, b, a] 15 | } 16 | 17 | pub fn to_tuple(&self) -> (f32, f32, f32, f32) { 18 | self.color 19 | } 20 | 21 | pub fn black() -> Color { 22 | Color::new(0., 0., 0., 1.) 23 | } 24 | pub fn white() -> Color { 25 | Color::new(1., 1., 1., 1.) 26 | } 27 | 28 | // :) 29 | pub fn rusty() -> Color { 30 | Color::new(0.64, 0.32, 0.18, 1.0) 31 | } 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Ox Ui 2 | High-level gui api for rust (early alpha). Ready to create simple ui, althout api may change. 3 | 4 | # Goals 5 | - flexible - can make different kinds of ui 6 | - easy 7 | - backend-agnostic, opengl on default 8 | - serializable - creating templates 9 | - editor mode - creating gui in editor 10 | - possible mobile framework 11 | 12 | # Inspirations 13 | - Unity3d 14 | - Flutter 15 | - React 16 | - Bootstrap 17 | 18 | # Implemented 19 | - Rectangle, Image, Text, Color, Button - components 20 | - Easy prototyping 21 | - Easy to serialize 22 | - Event system (buttons only for now) 23 | - State system (flutter/react like) - reloading only on state change 24 | 25 | # Need to be implemented 26 | - Window scrolling (html like view, considering mobile) 27 | - Input Field 28 | - Serialization 29 | - UI Visual Editor 30 | - Better Performance 31 | - Less boilerplate - more similar to flutter/html experience 32 | - Mobile 33 | - Divide library and example project to different repositories 34 | - Documentation 35 | 36 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | 2 | use color::Color; 3 | //todo font 4 | //todo font alignment 5 | 6 | //text component 7 | #[derive(Debug, Serialize,Deserialize)] 8 | pub struct Text { 9 | content: String, 10 | font_path: String, 11 | color: Color, 12 | font_size: u32, 13 | 14 | } 15 | impl Text { 16 | pub fn new(text: &str, font_path: &str) -> Text { 17 | Text { 18 | content: text.to_string(), 19 | font_path: font_path.to_string(), 20 | color: Color::black(), 21 | font_size: 32, 22 | } 23 | } 24 | pub fn get_content(&self) -> String { 25 | self.content.to_string() 26 | } 27 | 28 | pub fn set_color(&mut self, color: Color) { 29 | self.color = color; 30 | } 31 | pub fn get_color(&self) -> Color { 32 | self.color 33 | } 34 | pub fn get_font_size(&self) -> u32 { 35 | self.font_size 36 | } 37 | pub fn set_font_size(&mut self, font_size: u32) { 38 | self.font_size = font_size; 39 | } 40 | pub fn get_font_path(&self)->String 41 | { 42 | self.font_path.to_string() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jaroslaw Weber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/serialized_main_page.txt: -------------------------------------------------------------------------------- 1 | {"children":[{"children":[{"children":[],"text":{"content":"Simple Rust GUI!","font_path":"assets/JosefinSans-Regular.ttf","color":{"color":[1.0,1.0,1.0,1.0]},"font_size":32},"image":null,"color":null,"button":null,"name":"Rectangle","local_position":{"x":100.0,"y":100.0},"size":{"x":500.0,"y":150.0},"pivot":{"x":0.0,"y":1.0}}],"text":null,"image":null,"color":{"color":[0.64,0.32,0.18,1.0]},"button":null,"name":"Rectangle","local_position":{"x":0.0,"y":0.0},"size":{"x":500.0,"y":150.0},"pivot":{"x":0.5,"y":1.0}},{"children":[{"children":[],"text":{"content":"Lets rewrite GUI apps in rust!","font_path":"assets/JosefinSans-Regular.ttf","color":{"color":[0.0,0.0,0.0,1.0]},"font_size":20},"image":null,"color":null,"button":null,"name":"Rectangle","local_position":{"x":-20.0,"y":350.0},"size":{"x":100.0,"y":100.0},"pivot":{"x":0.0,"y":1.0}},{"children":[{"children":[],"text":{"content":"Click here!","font_path":"assets/JosefinSans-Regular.ttf","color":{"color":[1.0,1.0,1.0,1.0]},"font_size":32},"image":null,"color":null,"button":null,"name":"Rectangle","local_position":{"x":60.0,"y":70.0},"size":{"x":100.0,"y":100.0},"pivot":{"x":0.0,"y":1.0}}],"text":null,"image":null,"color":{"color":[0.64,0.32,0.18,1.0]},"button":{"id":1},"name":"Rectangle","local_position":{"x":0.0,"y":380.0},"size":{"x":300.0,"y":100.0},"pivot":{"x":0.5,"y":1.0}}],"text":null,"image":{"path":"assets/logo.png"},"color":null,"button":null,"name":"Rectangle","local_position":{"x":0.0,"y":-60.0},"size":{"x":300.0,"y":280.0},"pivot":{"x":0.5,"y":0.5}},{"children":[{"children":[],"text":{"content":"Footer!","font_path":"assets/JosefinSans-Regular.ttf","color":{"color":[1.0,1.0,1.0,1.0]},"font_size":32},"image":null,"color":null,"button":null,"name":"Rectangle","local_position":{"x":180.0,"y":60.0},"size":{"x":100.0,"y":100.0},"pivot":{"x":0.0,"y":1.0}}],"text":null,"image":null,"color":{"color":[0.64,0.32,0.18,1.0]},"button":null,"name":"Rectangle","local_position":{"x":0.0,"y":0.0},"size":{"x":500.0,"y":100.0},"pivot":{"x":0.5,"y":0.0}}],"text":null,"image":null,"color":{"color":[1.0,1.0,1.0,1.0]},"button":null,"name":"Rectangle","local_position":{"x":0.0,"y":0.0},"size":{"x":500.0,"y":800.0},"pivot":{"x":0.5,"y":0.5}} -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | 2 | use piston_window::*; 3 | use find_folder; 4 | use widget::Widget; 5 | use vector::Vector2; 6 | use rect::Rectangle; 7 | use text::Text; 8 | use std::path::Path; 9 | 10 | pub fn draw(widget: &Widget) { 11 | 12 | //unroll widget 13 | let builded: Rectangle = widget.build(); 14 | 15 | let size: Vector2 = builded.get_size(); 16 | let name: String = builded.get_name(); 17 | let mut window: PistonWindow = 18 | WindowSettings::new(name, (size.get_x() as u32, size.get_y() as u32)) 19 | .exit_on_esc(true) 20 | .build() 21 | .unwrap_or_else(|e| panic!("Failed to build PistonWindow: {}", e)); 22 | 23 | //font load 24 | let assets = find_folder::Search::ParentsThenKids(3, 3) 25 | .for_folder("assets") 26 | .unwrap(); 27 | let ref font = assets.join("JosefinSans-Regular.ttf"); 28 | let factory = window.factory.clone(); 29 | let mut glyphs = Glyphs::new(font, factory).unwrap(); 30 | //font load end 31 | let mut image_factory = window.factory.clone(); 32 | 33 | //todo pass builded 34 | let unrolled = UnrolledWidget::unroll_widget(&builded); 35 | 36 | while let Some(e) = window.next() { 37 | window.draw_2d(&e, |_c, g| for layer in unrolled.get_layers() { 38 | //parameters 39 | let p = layer.get_global_position(); 40 | let size = layer.get_size(); 41 | let x = p.get_x() as f64; 42 | let y = p.get_y() as f64; 43 | let w = size.get_x() as f64; 44 | let h = size.get_y() as f64; 45 | let tr = _c.transform.trans(w, h); 46 | //render color first 47 | match layer.get_color_fill() { 48 | Some(color) => { 49 | let slice = color.to_arr(); 50 | rectangle(slice, [x, y, w, h], _c.transform, g); 51 | } 52 | None => {} 53 | } 54 | //render text 55 | match layer.get_text() { 56 | &Some(ref txt) => { 57 | let txtContent: String = txt.get_content(); 58 | //todo text color 59 | let (_r, _g, _b, _a) = txt.get_color().to_tuple(); 60 | //println!("{:?}", tr); 61 | text::Text::new_color([_r, _g, _b, _a], txt.get_font_size()) 62 | .draw(&txtContent, &mut glyphs, &_c.draw_state, tr, g); 63 | } 64 | &None => {} 65 | } 66 | 67 | //render image 68 | match layer.get_image() { 69 | &Some(ref img) => { 70 | 71 | //let sq = rectangle::square(0.0, 0.0, 300.0); 72 | //let image=Image::new(); 73 | let image = Image::new().rect([x, y, w, h]); 74 | let path_str: String = img.get_path(); 75 | let path: &Path = Path::new(&path_str); 76 | let txt_set: TextureSettings = TextureSettings::new(); 77 | // let state=draw_state::state:: 78 | let texture = 79 | Texture::from_path(&mut image_factory, path, Flip::None, &txt_set).unwrap(); 80 | //println!("texture loaded: {:?}", texture); 81 | image.draw(&texture, &_c.draw_state, _c.transform, g); 82 | } 83 | &None => {} 84 | } 85 | 86 | }); 87 | } 88 | } 89 | fn from_slice(bytes: &[u8]) -> [u8; 4] { 90 | let mut a = [0; 4]; 91 | for i in 0..a.len() { 92 | // Panics if not enough input 93 | a[i] = bytes[i]; 94 | } 95 | a 96 | } 97 | 98 | struct UnrolledWidget<'a> { 99 | content: Vec<&'a Rectangle>, 100 | } 101 | 102 | impl<'a> UnrolledWidget<'a> { 103 | fn unroll_widget(rt: &Rectangle) -> UnrolledWidget { 104 | let mut rects = Vec::new(); 105 | let mut result = UnrolledWidget { content: rects }; 106 | let unrolled = result.unroll(rt); 107 | result.content = unrolled; 108 | result 109 | 110 | 111 | } 112 | 113 | fn unroll(&self, rt: &'a Rectangle) -> Vec<&'a Rectangle> { 114 | let mut v = Vec::new(); 115 | //println!("found rt!: {:?}", rt); 116 | v.push(rt); 117 | for ch in rt.get_children() { 118 | let unrolled: Vec<&Rectangle> = self.unroll(ch); 119 | for ur in unrolled { 120 | v.push(ur); 121 | } 122 | } 123 | v 124 | } 125 | 126 | 127 | 128 | fn get_size(&self) -> Vector2 { 129 | self.content.get(0).unwrap().get_size() 130 | } 131 | fn get_layers(&self) -> &Vec<&'a Rectangle> { 132 | &self.content 133 | } 134 | } -------------------------------------------------------------------------------- /src/rect.rs: -------------------------------------------------------------------------------- 1 | use text::Text; 2 | use vector::Vector2; 3 | use color::Color; 4 | use image::Image; 5 | use button::Button; 6 | 7 | //to json 8 | //rectangle containing different components. holds position and size info 9 | #[derive(Debug, Serialize, Deserialize)] 10 | pub struct Rectangle { 11 | children: Vec, 12 | text: Option, 13 | image: Option, 14 | color: Option, 15 | button: Option