├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── examples ├── run_app.rs └── run_event.rs └── src ├── gestures ├── hit_test.rs └── mod.rs ├── lib.rs ├── rendering ├── basic_types.rs ├── box.rs ├── flex.rs ├── image.rs ├── mod.rs ├── object.rs ├── proxy_box.rs ├── render_view.rs ├── sliver.rs └── sliver_list.rs ├── tests ├── common │ ├── box.rs │ ├── image.rs │ └── mod.rs ├── mod.rs └── rendering │ ├── flex_test.rs │ ├── image_test.rs │ ├── mod.rs │ └── slivers_test.rs ├── ui ├── app.rs ├── image.rs └── mod.rs └── widgets ├── constrained_box.rs ├── flex.rs ├── mod.rs ├── view.rs └── widget.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "layouter", 4 | "recomposer", 5 | "vtable", 6 | "vtables" 7 | ] 8 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "oxui" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | skia-safe = "0.47.0" 8 | winit = "0.26.1" 9 | skulpin = { version = "0.14", features = ["winit-app", "winit-latest"] } 10 | log = "0.4" 11 | typed-builder = "0.10.0" 12 | downcast-rs = "1.2.0" 13 | compose-rt = "0.12" 14 | 15 | [dev-dependencies] 16 | env_logger = "0.7" 17 | 18 | [profile.release] 19 | debug = true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OXUI 2 | Cross platform native GUI in Rust 3 | 4 | ## GUI with ideas from 5 | - Flutter 6 | - Widget Tree 7 | - Render Object 8 | - Skia 9 | - Jetpack Compose 10 | - Positional memorization runtime 11 | - [compose-rt](https://github.com/cksac/compose-rt) written in Rust 12 | - Incremental computation 13 | 14 | ## Status 15 | - Experimental and very early stage, it is far from usable. 16 | - Contribution is welcome if you also intrested in building GUI framework :) 17 | 18 | - [ ] Widgets 19 | - [x] Flex 20 | - [x] Constrained Box 21 | - [ ] ... 22 | - [ ] Rendering object 23 | - [x] RenderFlex 24 | - [ ] ... 25 | - [ ] App runner 26 | - [ ] event handling 27 | - [x] hit test 28 | - [ ] ... 29 | - [ ] optimization 30 | - [ ] repaint boundary 31 | - [ ] layer composition 32 | 33 | 34 | ## Examples 35 | - Nested Flex layout with positional state test 36 | - cargo run --example run_app 37 | 38 | https://user-images.githubusercontent.com/147393/158579311-0ac253f7-5cfc-464d-93d6-66e66dd288a0.mov 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/run_app.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::VecDeque; 3 | use std::rc::Rc; 4 | use std::time::{Duration, Instant}; 5 | 6 | use compose_rt::Recomposer; 7 | use oxui::rendering::{Axis, FlexFit, PipelineOwner, Size}; 8 | use oxui::rendering::{PaintContext, RenderBox}; 9 | use oxui::widgets::{BuildContext, ConstrainedBox, Flex, Widget}; 10 | use skulpin::app::AppDrawArgs; 11 | use skulpin::app::AppError; 12 | use skulpin::app::AppHandler; 13 | use skulpin::app::AppUpdateArgs; 14 | use skulpin::app::{AppBuilder, MouseButton}; 15 | use skulpin::CoordinateSystem; 16 | use skulpin::LogicalSize; 17 | 18 | #[derive(Debug)] 19 | pub struct RootWidget; 20 | impl Widget for RootWidget { 21 | #[track_caller] 22 | fn create(&self, context: BuildContext) -> Rc> { 23 | let state = context.state(|| { 24 | Rc::new(RefCell::new( 25 | (2usize..57).chain((3usize..=58).rev()).cycle(), 26 | )) 27 | }); 28 | let count: usize = state.borrow_mut().next().unwrap(); 29 | 30 | let mut children = Vec::new(); 31 | for j in 1..=count { 32 | children.push({ 33 | let v_state = context.state(|| { 34 | Rc::new(RefCell::new( 35 | (2usize..257).chain((3usize..=258).rev()).cycle(), //(2usize..4).cycle(), 36 | )) 37 | }); 38 | let v_count: usize = v_state.borrow_mut().next().unwrap(); 39 | let mut children = Vec::new(); 40 | for i in 1..=v_count { 41 | children 42 | .push(ConstrainedBox::default().into_flexible(i as usize, FlexFit::Loose)) 43 | } 44 | 45 | Flex::builder() 46 | .direction(Axis::Vertical) 47 | .children(children) 48 | .build() 49 | .into_flexible(j, FlexFit::Loose) 50 | }); 51 | } 52 | 53 | Flex::builder().children(children).build().create(context) 54 | } 55 | } 56 | 57 | struct App { 58 | recomposer: Recomposer, 59 | pipeline: PipelineOwner, 60 | previous_clicks: VecDeque, 61 | previous_frame: Instant, 62 | } 63 | 64 | impl App { 65 | pub fn new(width: u32, height: u32, root: W) -> Self 66 | where 67 | W: 'static + Widget, 68 | { 69 | App { 70 | recomposer: Recomposer::new(1000), 71 | pipeline: PipelineOwner::new(Size::new(width as f32, height as f32), root), 72 | previous_clicks: VecDeque::new(), 73 | previous_frame: Instant::now(), 74 | } 75 | } 76 | } 77 | 78 | impl AppHandler for App { 79 | fn update(&mut self, update_args: AppUpdateArgs) { 80 | // let input_state = update_args.input_state; 81 | // let app_control = update_args.app_control; 82 | 83 | // if input_state.is_key_down(VirtualKeyCode::Escape) { 84 | // app_control.enqueue_terminate_process(); 85 | // } 86 | if update_args 87 | .input_state 88 | .is_mouse_just_down(MouseButton::Left) 89 | { 90 | self.previous_clicks.push_back(true); 91 | } 92 | } 93 | 94 | fn draw(&mut self, draw_args: AppDrawArgs) { 95 | // click to next frame 96 | //if let Some(_) = self.previous_clicks.pop_front() { 97 | if self.previous_frame.elapsed() > Duration::from_millis(16) { 98 | let canvas = draw_args.canvas; 99 | canvas.clear(0); 100 | 101 | let mut context = PaintContext::new(canvas); 102 | 103 | self.recomposer.compose(|cx| { 104 | self.pipeline.draw_frame(cx, &mut context); 105 | }); 106 | 107 | self.previous_frame = draw_args.time_state.current_instant(); 108 | } 109 | } 110 | 111 | fn fatal_error(&mut self, error: &AppError) { 112 | println!("{}", error); 113 | } 114 | } 115 | 116 | // This example shows how to use the "app" helpers to get a window open and drawing with minimal code 117 | // It's not as flexible as working with winit directly, but it's quick and simple 118 | fn main() { 119 | // Setup logging 120 | env_logger::Builder::from_default_env() 121 | .filter_level(log::LevelFilter::Debug) 122 | .filter_module("compose_rt", log::LevelFilter::Info) 123 | .init(); 124 | 125 | // Set up the coordinate system to be fixed at 900x600, and use this as the default window size 126 | // This means the drawing code can be written as though the window is always 900x600. The 127 | // output will be automatically scaled so that it's always visible. 128 | let logical_size = LogicalSize::new(900, 600); 129 | 130 | let app = App::new(logical_size.width, logical_size.height, RootWidget); 131 | 132 | let visible_range = skulpin::skia_safe::Rect { 133 | left: 0.0, 134 | right: logical_size.width as f32, 135 | top: 0.0, 136 | bottom: logical_size.height as f32, 137 | }; 138 | let scale_to_fit = skulpin::skia_safe::matrix::ScaleToFit::Center; 139 | 140 | AppBuilder::new() 141 | .inner_size(logical_size) 142 | .coordinate_system(CoordinateSystem::VisibleRange(visible_range, scale_to_fit)) 143 | .run(app); 144 | } 145 | -------------------------------------------------------------------------------- /examples/run_event.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::VecDeque; 3 | use std::rc::Rc; 4 | use std::time::Instant; 5 | 6 | use compose_rt::Recomposer; 7 | use oxui::rendering::{Axis, FlexFit, Offset, PipelineOwner, Size}; 8 | use oxui::rendering::{PaintContext, RenderBox}; 9 | use oxui::widgets::{BuildContext, ConstrainedBox, Flex, Widget}; 10 | use skulpin::app::AppDrawArgs; 11 | use skulpin::app::AppError; 12 | use skulpin::app::AppHandler; 13 | use skulpin::app::AppUpdateArgs; 14 | use skulpin::app::{AppBuilder, MouseButton}; 15 | use skulpin::CoordinateSystem; 16 | use skulpin::LogicalSize; 17 | 18 | #[derive(Debug)] 19 | pub struct RootWidget; 20 | impl Widget for RootWidget { 21 | #[track_caller] 22 | fn create(&self, context: BuildContext) -> Rc> { 23 | let state = context.state(|| { 24 | Rc::new(RefCell::new( 25 | (2usize..57).chain((3usize..=58).rev()).cycle(), 26 | )) 27 | }); 28 | let count: usize = state.borrow_mut().next().unwrap(); 29 | 30 | let mut children = Vec::new(); 31 | for j in 1..=count { 32 | children.push({ 33 | let v_state = context.state(|| { 34 | Rc::new(RefCell::new( 35 | (2usize..57).chain((3usize..=58).rev()).cycle(), //(2usize..4).cycle(), 36 | )) 37 | }); 38 | let v_count: usize = v_state.borrow_mut().next().unwrap(); 39 | let mut children = Vec::new(); 40 | for i in 1..=v_count { 41 | children 42 | .push(ConstrainedBox::default().into_flexible(i as usize, FlexFit::Loose)) 43 | } 44 | 45 | Flex::builder() 46 | .direction(Axis::Vertical) 47 | .children(children) 48 | .build() 49 | .into_flexible(j, FlexFit::Loose) 50 | }); 51 | } 52 | 53 | Flex::builder().children(children).build().create(context) 54 | } 55 | } 56 | 57 | struct App { 58 | recomposer: Recomposer, 59 | pipeline: PipelineOwner, 60 | previous_clicks: VecDeque, 61 | previous_frame: Instant, 62 | } 63 | 64 | impl App { 65 | pub fn new(width: u32, height: u32, root: W) -> Self 66 | where 67 | W: 'static + Widget, 68 | { 69 | App { 70 | recomposer: Recomposer::new(1000), 71 | pipeline: PipelineOwner::new(Size::new(width as f32, height as f32), root), 72 | previous_clicks: VecDeque::new(), 73 | previous_frame: Instant::now(), 74 | } 75 | } 76 | } 77 | 78 | impl AppHandler for App { 79 | fn update(&mut self, update_args: AppUpdateArgs) { 80 | // let input_state = update_args.input_state; 81 | // let app_control = update_args.app_control; 82 | 83 | // if input_state.is_key_down(VirtualKeyCode::Escape) { 84 | // app_control.enqueue_terminate_process(); 85 | // } 86 | if update_args 87 | .input_state 88 | .is_mouse_just_down(MouseButton::Left) 89 | { 90 | let p = update_args.input_state.mouse_position(); 91 | let position = Offset::new(p.x as f32, p.y as f32); 92 | self.pipeline.handle_event(position); 93 | self.previous_clicks.push_back(position); 94 | } 95 | } 96 | 97 | fn draw(&mut self, draw_args: AppDrawArgs) { 98 | // click to next frame 99 | if let Some(_) = self.previous_clicks.pop_front() { 100 | //if self.previous_frame.elapsed() > Duration::from_millis(100) { 101 | let canvas = draw_args.canvas; 102 | canvas.clear(0); 103 | 104 | let mut context = PaintContext::new(canvas); 105 | self.recomposer.compose(|cx| { 106 | self.pipeline.draw_frame(cx, &mut context); 107 | }); 108 | self.previous_frame = draw_args.time_state.current_instant(); 109 | } 110 | } 111 | 112 | fn fatal_error(&mut self, error: &AppError) { 113 | println!("{}", error); 114 | } 115 | } 116 | 117 | // This example shows how to use the "app" helpers to get a window open and drawing with minimal code 118 | // It's not as flexible as working with winit directly, but it's quick and simple 119 | fn main() { 120 | // Setup logging 121 | env_logger::Builder::from_default_env() 122 | .filter_level(log::LevelFilter::Warn) 123 | .filter_module("compose_rt", log::LevelFilter::Debug) 124 | .init(); 125 | 126 | // Set up the coordinate system to be fixed at 900x600, and use this as the default window size 127 | // This means the drawing code can be written as though the window is always 900x600. The 128 | // output will be automatically scaled so that it's always visible. 129 | let logical_size = LogicalSize::new(900, 600); 130 | 131 | let app = App::new(logical_size.width, logical_size.height, RootWidget); 132 | 133 | let visible_range = skulpin::skia_safe::Rect { 134 | left: 0.0, 135 | right: logical_size.width as f32, 136 | top: 0.0, 137 | bottom: logical_size.height as f32, 138 | }; 139 | let scale_to_fit = skulpin::skia_safe::matrix::ScaleToFit::Center; 140 | 141 | AppBuilder::new() 142 | .inner_size(logical_size) 143 | .coordinate_system(CoordinateSystem::VisibleRange(visible_range, scale_to_fit)) 144 | .run(app); 145 | } 146 | -------------------------------------------------------------------------------- /src/gestures/hit_test.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, fmt::Debug, rc::Rc}; 2 | 3 | use crate::rendering::Offset; 4 | 5 | pub trait HitTestTarget: Debug { 6 | fn handle_event(&mut self, event: PointerEvent, entry: HitTestEntry) {} 7 | } 8 | 9 | #[derive(Debug)] 10 | pub struct HitTestResult { 11 | pub(crate) path: Vec, 12 | } 13 | 14 | impl HitTestResult { 15 | pub fn new() -> HitTestResult { 16 | HitTestResult { path: Vec::new() } 17 | } 18 | 19 | pub fn add(&mut self, entry: HitTestEntry) { 20 | self.path.push(entry) 21 | } 22 | } 23 | 24 | #[derive(Debug, Clone)] 25 | pub struct HitTestEntry { 26 | pub(crate) target: Rc>, 27 | //transform: Option 28 | } 29 | 30 | impl HitTestEntry { 31 | pub fn new(target: Rc>) -> Self { 32 | HitTestEntry { target } 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct PointerEvent { 38 | down: bool, 39 | position: Offset, 40 | } 41 | 42 | impl PointerEvent { 43 | pub fn new(position: Offset) -> Self { 44 | PointerEvent { 45 | down: false, 46 | position, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/gestures/mod.rs: -------------------------------------------------------------------------------- 1 | mod hit_test; 2 | pub use hit_test::*; 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(trait_upcasting)] 2 | 3 | pub mod gestures; 4 | pub mod rendering; 5 | pub mod ui; 6 | pub mod widgets; 7 | 8 | #[cfg(test)] 9 | mod tests; 10 | -------------------------------------------------------------------------------- /src/rendering/basic_types.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq, Default)] 2 | pub struct Size { 3 | pub width: f32, 4 | pub height: f32, 5 | } 6 | 7 | impl Size { 8 | pub fn new(width: f32, height: f32) -> Self { 9 | Self { width, height } 10 | } 11 | 12 | pub fn zero() -> Self { 13 | Self { 14 | width: 0.0, 15 | height: 0.0, 16 | } 17 | } 18 | 19 | pub fn is_empty(&self) -> bool { 20 | self.width <= 0.0 || self.height <= 0.0 21 | } 22 | 23 | pub fn main_size(&self, direction: Axis) -> f32 { 24 | match direction { 25 | Axis::Horizontal => self.width, 26 | Axis::Vertical => self.height, 27 | } 28 | } 29 | 30 | pub fn cross_size(&self, direction: Axis) -> f32 { 31 | match direction { 32 | Axis::Horizontal => self.height, 33 | Axis::Vertical => self.width, 34 | } 35 | } 36 | 37 | pub fn contains(&self, offset: Offset) -> bool { 38 | offset.x >= 0.0 && offset.x < self.width && offset.y >= 0.0 && offset.y < self.height 39 | } 40 | } 41 | 42 | impl From<(f32, f32)> for Size { 43 | fn from(val: (f32, f32)) -> Self { 44 | Self { 45 | width: val.0, 46 | height: val.1, 47 | } 48 | } 49 | } 50 | 51 | #[derive(Debug, Clone, Copy, PartialEq)] 52 | pub struct Offset { 53 | pub x: f32, 54 | pub y: f32, 55 | } 56 | 57 | impl Offset { 58 | pub fn new(x: f32, y: f32) -> Self { 59 | Self { x, y } 60 | } 61 | 62 | pub fn zero() -> Self { 63 | Self { x: 0.0, y: 0.0 } 64 | } 65 | } 66 | 67 | impl std::ops::Add for Offset { 68 | type Output = Self; 69 | fn add(self, rhs: Offset) -> Self::Output { 70 | Offset { 71 | x: self.x + rhs.x, 72 | y: self.y + rhs.y, 73 | } 74 | } 75 | } 76 | 77 | impl std::ops::Sub for Offset { 78 | type Output = Self; 79 | fn sub(self, rhs: Offset) -> Self::Output { 80 | Offset { 81 | x: self.x - rhs.x, 82 | y: self.y - rhs.y, 83 | } 84 | } 85 | } 86 | 87 | impl std::ops::AddAssign for Offset { 88 | fn add_assign(&mut self, rhs: Offset) { 89 | self.x += rhs.x; 90 | self.y += rhs.y; 91 | } 92 | } 93 | 94 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 95 | pub enum Axis { 96 | Horizontal, 97 | Vertical, 98 | } 99 | 100 | impl Axis { 101 | pub fn flip(&self) -> Self { 102 | match self { 103 | Axis::Horizontal => Axis::Vertical, 104 | Axis::Vertical => Axis::Horizontal, 105 | } 106 | } 107 | } 108 | 109 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 110 | pub enum VerticalDirection { 111 | Down, 112 | Up, 113 | } 114 | 115 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 116 | pub enum AxisDirection { 117 | Up, 118 | Right, 119 | Down, 120 | Left, 121 | } 122 | 123 | impl From for Axis { 124 | fn from(dir: AxisDirection) -> Self { 125 | match dir { 126 | AxisDirection::Up | AxisDirection::Down => Axis::Vertical, 127 | AxisDirection::Left | AxisDirection::Right => Axis::Horizontal, 128 | } 129 | } 130 | } 131 | 132 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 133 | pub enum ScrollDirection { 134 | Idle, 135 | Forward, 136 | Reverse, 137 | } 138 | 139 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 140 | pub enum TextBaseline { 141 | Alphabetic, 142 | Ideographic, 143 | } 144 | 145 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 146 | pub enum TextDirection { 147 | /// The text flows from right to left (e.g. Arabic, Hebrew). 148 | RTL, 149 | 150 | /// The text flows from left to right (e.g., English, French). 151 | LTR, 152 | } 153 | 154 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 155 | pub enum Clip { 156 | None, 157 | HardEdge, 158 | AntiAlias, 159 | AntiAliasWithSaveLayer, 160 | } 161 | -------------------------------------------------------------------------------- /src/rendering/box.rs: -------------------------------------------------------------------------------- 1 | use crate::rendering::{Offset, RenderObject, Size}; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq)] 4 | pub struct BoxConstraints { 5 | pub min_width: f32, 6 | pub max_width: f32, 7 | pub min_height: f32, 8 | pub max_height: f32, 9 | } 10 | 11 | impl Default for BoxConstraints { 12 | fn default() -> Self { 13 | Self { 14 | min_width: 0.0, 15 | max_width: f32::INFINITY, 16 | min_height: 0.0, 17 | max_height: f32::INFINITY, 18 | } 19 | } 20 | } 21 | 22 | impl BoxConstraints { 23 | pub fn with_min_width(mut self, min_width: f32) -> Self { 24 | self.min_width = min_width; 25 | self 26 | } 27 | 28 | pub fn with_max_width(mut self, max_width: f32) -> Self { 29 | self.max_width = max_width; 30 | self 31 | } 32 | 33 | pub fn with_max_height(mut self, max_height: f32) -> Self { 34 | self.max_height = max_height; 35 | self 36 | } 37 | 38 | pub fn with_min_height(mut self, min_height: f32) -> Self { 39 | self.min_height = min_height; 40 | self 41 | } 42 | 43 | pub fn tight(size: impl Into) -> Self { 44 | let size = size.into(); 45 | Self { 46 | min_width: size.width, 47 | max_width: size.width, 48 | min_height: size.height, 49 | max_height: size.height, 50 | } 51 | } 52 | 53 | pub fn tight_for(width: impl Into>, height: impl Into>) -> Self { 54 | let (min_width, max_width) = width.into().map(|v| (v, v)).unwrap_or((0.0, f32::INFINITY)); 55 | let (min_height, max_height) = height 56 | .into() 57 | .map(|v| (v, v)) 58 | .unwrap_or((0.0, f32::INFINITY)); 59 | 60 | Self { 61 | min_width, 62 | max_width, 63 | min_height, 64 | max_height, 65 | } 66 | } 67 | pub fn expand() -> Self { 68 | BoxConstraints::expand_by(None, None) 69 | } 70 | 71 | pub fn expand_by(width: impl Into>, height: impl Into>) -> Self { 72 | let (min_width, max_width) = width 73 | .into() 74 | .map(|v| (v, v)) 75 | .unwrap_or((f32::INFINITY, f32::INFINITY)); 76 | let (min_height, max_height) = height 77 | .into() 78 | .map(|v| (v, v)) 79 | .unwrap_or((f32::INFINITY, f32::INFINITY)); 80 | 81 | Self { 82 | min_width, 83 | max_width, 84 | min_height, 85 | max_height, 86 | } 87 | } 88 | 89 | pub fn enforce(&self, other: &BoxConstraints) -> Self { 90 | Self { 91 | min_width: self.min_width.clamp(other.min_width, other.max_width), 92 | max_width: self.max_width.clamp(other.min_width, other.max_width), 93 | min_height: self.min_height.clamp(other.min_height, other.max_height), 94 | max_height: self.max_height.clamp(other.min_height, other.max_height), 95 | } 96 | } 97 | 98 | pub fn has_tight_width(&self) -> bool { 99 | self.min_width >= self.max_width 100 | } 101 | 102 | pub fn has_tight_height(&self) -> bool { 103 | self.min_height >= self.max_height 104 | } 105 | 106 | pub fn is_tight(&self) -> bool { 107 | self.has_tight_width() && self.has_tight_height() 108 | } 109 | 110 | pub fn constrain_width(&self, width: f32) -> f32 { 111 | width.clamp(self.min_width, self.max_width) 112 | } 113 | 114 | pub fn constrain_height(&self, height: f32) -> f32 { 115 | height.clamp(self.min_height, self.max_height) 116 | } 117 | 118 | pub fn biggest(&self) -> Size { 119 | Size { 120 | width: self.constrain_width(f32::INFINITY), 121 | height: self.constrain_height(f32::INFINITY), 122 | } 123 | } 124 | 125 | pub fn smallest(&self) -> Size { 126 | Size { 127 | width: self.constrain_width(0.0), 128 | height: self.constrain_height(0.0), 129 | } 130 | } 131 | 132 | pub fn constrain(&self, size: impl Into) -> Size { 133 | let size = size.into(); 134 | Size { 135 | width: self.constrain_width(size.width), 136 | height: self.constrain_height(size.height), 137 | } 138 | } 139 | 140 | pub fn constrain_with_aspect_ratio(&self, size: impl Into) -> Size { 141 | let size = size.into(); 142 | if self.is_tight() { 143 | self.smallest() 144 | } else { 145 | let mut width = size.width; 146 | let mut height = size.height; 147 | let aspect_ratio = width / height; 148 | 149 | if width > self.max_width { 150 | width = self.max_width; 151 | height = width / aspect_ratio; 152 | } 153 | 154 | if height > self.max_height { 155 | height = self.max_height; 156 | width = height * aspect_ratio; 157 | } 158 | 159 | if width < self.min_width { 160 | width = self.min_width; 161 | height = width / aspect_ratio; 162 | } 163 | 164 | if height < self.min_height { 165 | height = self.min_height; 166 | width = height * aspect_ratio; 167 | } 168 | 169 | Size { 170 | width: self.constrain_width(width), 171 | height: self.constrain_height(height), 172 | } 173 | } 174 | } 175 | } 176 | 177 | pub trait RenderBox: RenderObject { 178 | fn layout(&mut self, constraints: &BoxConstraints, parent_use_size: bool) { 179 | self.perform_layout(constraints) 180 | } 181 | 182 | fn perform_layout(&mut self, constraints: &BoxConstraints); 183 | 184 | fn perform_resize(&mut self, constraints: &BoxConstraints); 185 | 186 | fn size(&self) -> Size; 187 | } 188 | -------------------------------------------------------------------------------- /src/rendering/flex.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::{type_name, TypeId}, 3 | cell::RefCell, 4 | ops::Deref, 5 | rc::Rc, 6 | }; 7 | 8 | use crate::{ 9 | gestures::{HitTestEntry, HitTestResult, HitTestTarget}, 10 | rendering::{ 11 | Axis, BoxConstraints, Clip, Offset, PaintContext, RenderBox, RenderObject, Size, 12 | TextBaseline, TextDirection, VerticalDirection, 13 | }, 14 | }; 15 | 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 17 | pub enum FlexFit { 18 | Tight, 19 | Loose, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 23 | pub enum CrossAxisAlignment { 24 | Start, 25 | End, 26 | Center, 27 | Stretch, 28 | Baseline, 29 | } 30 | 31 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 32 | pub enum MainAxisAlignment { 33 | Start, 34 | End, 35 | Center, 36 | SpaceBetween, 37 | SpaceAround, 38 | SpaceEvenly, 39 | } 40 | 41 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 42 | pub enum MainAxisSize { 43 | Min, 44 | Max, 45 | } 46 | 47 | #[derive(Debug)] 48 | pub struct RenderFlex { 49 | // RenderBox 50 | pub(crate) size: Size, 51 | 52 | // RenderFlex 53 | pub(crate) children: Vec, 54 | pub(crate) direction: Axis, 55 | pub(crate) main_axis_size: MainAxisSize, 56 | pub(crate) main_axis_alignment: MainAxisAlignment, 57 | pub(crate) cross_axis_alignment: CrossAxisAlignment, 58 | pub(crate) vertical_direction: VerticalDirection, 59 | pub(crate) text_direction: Option, 60 | pub(crate) text_baseline: Option, 61 | pub(crate) clip_behavior: Clip, 62 | pub(crate) _overflow: f32, 63 | } 64 | 65 | impl RenderFlex { 66 | pub fn with_direction(mut self, direction: Axis) -> Self { 67 | self.direction = direction; 68 | self 69 | } 70 | 71 | pub fn with_text_direction(mut self, text_direction: impl Into>) -> Self { 72 | self.text_direction = text_direction.into(); 73 | self 74 | } 75 | 76 | pub fn with_vertical_direction(mut self, vertical_direction: VerticalDirection) -> Self { 77 | self.vertical_direction = vertical_direction; 78 | self 79 | } 80 | 81 | pub fn with_child(mut self, child: impl Into) -> Self { 82 | self.children.push(child.into()); 83 | self 84 | } 85 | } 86 | 87 | impl Default for RenderFlex { 88 | fn default() -> Self { 89 | Self { 90 | size: Size::zero(), 91 | children: Vec::new(), 92 | direction: Axis::Horizontal, 93 | main_axis_size: MainAxisSize::Max, 94 | main_axis_alignment: MainAxisAlignment::Start, 95 | cross_axis_alignment: CrossAxisAlignment::Center, 96 | vertical_direction: VerticalDirection::Down, 97 | text_direction: None, 98 | text_baseline: None, 99 | clip_behavior: Clip::None, 100 | _overflow: 0.0, 101 | } 102 | } 103 | } 104 | 105 | #[derive(Debug, Clone)] 106 | pub struct RenderFlexible { 107 | pub(crate) offset: Offset, 108 | pub(crate) flex: usize, 109 | pub(crate) fit: FlexFit, 110 | pub(crate) inner: Rc>, 111 | } 112 | 113 | impl RenderFlexible { 114 | pub fn new(child: T, flex: usize, fit: FlexFit) -> Self 115 | where 116 | T: 'static + RenderBox, 117 | { 118 | RenderFlexible { 119 | offset: Offset::zero(), 120 | flex, 121 | fit, 122 | inner: Rc::new(RefCell::new(child)), 123 | } 124 | } 125 | } 126 | 127 | struct LayoutSizes { 128 | main_size: f32, 129 | cross_size: f32, 130 | allocated_size: f32, 131 | } 132 | 133 | impl RenderFlex { 134 | fn compute_sizes(&mut self, constraints: &BoxConstraints) -> LayoutSizes { 135 | // Determine used flex factor, size inflexible items, calculate free space. 136 | let mut total_flex = 0.0f32; 137 | let max_main_size = match self.direction { 138 | Axis::Horizontal => constraints.max_width, 139 | Axis::Vertical => constraints.max_height, 140 | }; 141 | let can_flex = max_main_size < f32::INFINITY; 142 | 143 | let mut cross_size = 0.0f32; 144 | let mut allocated_size = 0.0f32; 145 | let mut last_flex_child_idx = 0; 146 | 147 | for (child_idx, child) in self.children.iter_mut().enumerate() { 148 | if child.flex > 0 { 149 | total_flex += child.flex as f32; 150 | last_flex_child_idx = child_idx; 151 | } else { 152 | let inner_constraints = match self.cross_axis_alignment { 153 | CrossAxisAlignment::Stretch => match self.direction { 154 | Axis::Horizontal => BoxConstraints::tight_for(constraints.max_height, None), 155 | Axis::Vertical => BoxConstraints::tight_for(None, constraints.max_height), 156 | }, 157 | _ => match self.direction { 158 | Axis::Horizontal => { 159 | BoxConstraints::default().with_max_height(constraints.max_height) 160 | } 161 | Axis::Vertical => { 162 | BoxConstraints::default().with_max_width(constraints.max_width) 163 | } 164 | }, 165 | }; 166 | child.inner.borrow_mut().layout(&inner_constraints, true); 167 | let child_size = child.inner.borrow_mut().size(); 168 | allocated_size += child_size.main_size(self.direction); 169 | cross_size = cross_size.max(child_size.cross_size(self.direction)); 170 | } 171 | } 172 | 173 | // Distribute free space to flexible children. 174 | let free_space = ((if can_flex { max_main_size } else { 0.0 }) - allocated_size).max(0.0); 175 | let mut allocated_flex_space = 0.0f32; 176 | if total_flex > 0.0 { 177 | let space_per_flex = if can_flex { 178 | free_space / total_flex 179 | } else { 180 | f32::NAN 181 | }; 182 | 183 | for (child_idx, child) in self.children.iter_mut().enumerate() { 184 | if child.flex > 0 { 185 | let max_child_extent = if can_flex { 186 | if child_idx == last_flex_child_idx { 187 | free_space - allocated_flex_space 188 | } else { 189 | space_per_flex * child.flex as f32 190 | } 191 | } else { 192 | f32::INFINITY 193 | }; 194 | let min_child_extend = match child.fit { 195 | FlexFit::Tight => max_child_extent, 196 | FlexFit::Loose => 0.0, 197 | }; 198 | let inner_constraints = match self.cross_axis_alignment { 199 | CrossAxisAlignment::Stretch => match self.direction { 200 | Axis::Horizontal => BoxConstraints { 201 | min_width: min_child_extend, 202 | max_width: max_child_extent, 203 | min_height: constraints.max_height, 204 | max_height: constraints.max_height, 205 | }, 206 | Axis::Vertical => BoxConstraints { 207 | min_width: constraints.max_width, 208 | max_width: constraints.max_width, 209 | min_height: min_child_extend, 210 | max_height: max_child_extent, 211 | }, 212 | }, 213 | _ => match self.direction { 214 | Axis::Horizontal => BoxConstraints { 215 | min_width: min_child_extend, 216 | max_width: max_child_extent, 217 | min_height: 0.0, 218 | max_height: constraints.max_height, 219 | }, 220 | Axis::Vertical => BoxConstraints { 221 | min_width: 0.0, 222 | max_width: constraints.max_width, 223 | min_height: min_child_extend, 224 | max_height: max_child_extent, 225 | }, 226 | }, 227 | }; 228 | 229 | child.inner.borrow_mut().layout(&inner_constraints, true); 230 | let child_size = child.inner.borrow().size(); 231 | 232 | let child_main_size = child_size.main_size(self.direction); 233 | allocated_size += child_main_size; 234 | allocated_flex_space += child_main_size; 235 | cross_size = cross_size.max(child_size.cross_size(self.direction)); 236 | } 237 | } 238 | } 239 | 240 | let ideal_size = if can_flex && self.main_axis_size == MainAxisSize::Max { 241 | max_main_size 242 | } else { 243 | allocated_size 244 | }; 245 | 246 | LayoutSizes { 247 | main_size: ideal_size, 248 | cross_size, 249 | allocated_size, 250 | } 251 | } 252 | } 253 | 254 | impl HitTestTarget for RenderFlex {} 255 | 256 | impl RenderObject for RenderFlex { 257 | fn ty_id(&self) -> std::any::TypeId { 258 | TypeId::of::() 259 | } 260 | 261 | fn ty_name(&self) -> &'static str { 262 | type_name::() 263 | } 264 | 265 | fn paint(&self, context: &mut PaintContext, offset: Offset) { 266 | context.draw_rect(offset, self.size); 267 | for child in &self.children { 268 | child 269 | .inner 270 | .borrow_mut() 271 | .paint(context, child.offset + offset); 272 | } 273 | } 274 | 275 | fn hit_test(&self, position: Offset, result: &mut HitTestResult) -> bool { 276 | let mut is_hit = false; 277 | for child in &self.children { 278 | let transformed = position - child.offset; 279 | if child.inner.borrow().hit_test(transformed, result) { 280 | is_hit = true; 281 | let entry = HitTestEntry::new(child.inner.clone()); 282 | result.add(entry); 283 | } 284 | } 285 | is_hit 286 | } 287 | } 288 | 289 | impl RenderBox for RenderFlex { 290 | fn size(&self) -> Size { 291 | self.size 292 | } 293 | 294 | fn perform_layout(&mut self, constraints: &BoxConstraints) { 295 | let sizes = self.compute_sizes(constraints); 296 | let allocated_size = sizes.allocated_size; 297 | let mut actual_size = sizes.main_size; 298 | let mut cross_size = sizes.cross_size; 299 | 300 | let max_baseline_distance = 0.0f32; 301 | // TODO: handle baseline alignment 302 | // if self.cross_axis_alignment == CrossAxisAlignment::Baseline { 303 | // let max_size_above_baseline = 0.0f32; 304 | // let max_size_below_baseline = 0.0f32; 305 | // for child in self.children.iter_mut() { 306 | // debug_assert!( 307 | // self.text_baseline.is_none(), 308 | // "you must also specify which baseline to use" 309 | // ) 310 | // } 311 | // } 312 | 313 | // Align items along the main axis. 314 | match self.direction { 315 | Axis::Horizontal => { 316 | let size = constraints.constrain((actual_size, cross_size)); 317 | actual_size = size.width; 318 | cross_size = size.height; 319 | self.size = size; 320 | } 321 | Axis::Vertical => { 322 | let size = constraints.constrain((cross_size, actual_size)); 323 | actual_size = size.height; 324 | cross_size = size.width; 325 | self.size = size; 326 | } 327 | } 328 | let actual_size_delta = actual_size - allocated_size; 329 | self._overflow = (-actual_size_delta).max(0.0); 330 | let remaining_space = actual_size.max(0.0); 331 | 332 | let children_count = self.children.len(); 333 | let (leading_space, between_space) = match self.main_axis_alignment { 334 | MainAxisAlignment::Start => (0.0, 0.0), 335 | MainAxisAlignment::End => (remaining_space, 0.0), 336 | MainAxisAlignment::Center => (remaining_space / 2.0, 0.0), 337 | MainAxisAlignment::SpaceBetween => ( 338 | 0.0, 339 | if children_count > 1 { 340 | (children_count - 1) as f32 341 | } else { 342 | 0.0 343 | }, 344 | ), 345 | MainAxisAlignment::SpaceAround => { 346 | let between_space = if children_count > 1 { 347 | remaining_space / children_count as f32 348 | } else { 349 | 0.0 350 | }; 351 | let leading_space = between_space / 2.0; 352 | (leading_space, between_space) 353 | } 354 | MainAxisAlignment::SpaceEvenly => { 355 | let between_space = if children_count > 1 { 356 | remaining_space / (children_count + 1) as f32 357 | } else { 358 | 0.0 359 | }; 360 | (between_space, between_space) 361 | } 362 | }; 363 | 364 | let flip_main_axis = 365 | !start_is_top_left(self.direction, self.text_direction, self.vertical_direction) 366 | .unwrap_or(true); 367 | 368 | // Position elements 369 | let mut child_main_position = if flip_main_axis { 370 | actual_size - leading_space 371 | } else { 372 | leading_space 373 | }; 374 | for child in self.children.iter_mut() { 375 | let child_size = child.inner.borrow().size(); 376 | let child_cross_position = match self.cross_axis_alignment { 377 | CrossAxisAlignment::Start | CrossAxisAlignment::End => { 378 | if start_is_top_left( 379 | self.direction.flip(), 380 | self.text_direction, 381 | self.vertical_direction, 382 | ) 383 | .unwrap_or(false) 384 | == (self.cross_axis_alignment == CrossAxisAlignment::Start) 385 | { 386 | 0.0 387 | } else { 388 | cross_size - child_size.cross_size(self.direction) 389 | } 390 | } 391 | CrossAxisAlignment::Center => cross_size - child_size.cross_size(self.direction), 392 | CrossAxisAlignment::Stretch => 0.0, 393 | CrossAxisAlignment::Baseline => { 394 | match self.direction { 395 | Axis::Horizontal => { 396 | // TODO: child.getDistanceToBaseline 397 | let distance: Option = None; 398 | match distance { 399 | Some(d) => max_baseline_distance - d, 400 | None => 0.0, 401 | } 402 | } 403 | Axis::Vertical => 0.0, 404 | } 405 | } 406 | }; 407 | 408 | if flip_main_axis { 409 | child_main_position = child_size.main_size(self.direction); 410 | } 411 | 412 | child.offset = match self.direction { 413 | Axis::Horizontal => Offset::new(child_main_position, child_cross_position), 414 | Axis::Vertical => Offset::new(child_cross_position, child_main_position), 415 | }; 416 | 417 | if flip_main_axis { 418 | child_main_position -= between_space; 419 | } else { 420 | child_main_position += child_size.main_size(self.direction) + between_space; 421 | } 422 | } 423 | } 424 | fn perform_resize(&mut self, constraints: &BoxConstraints) { 425 | todo!() 426 | } 427 | } 428 | 429 | fn start_is_top_left( 430 | direction: Axis, 431 | text_direction: impl Into>, 432 | vertical_direction: impl Into>, 433 | ) -> Option { 434 | match direction { 435 | Axis::Horizontal => match text_direction.into() { 436 | Some(TextDirection::LTR) => Some(true), 437 | Some(TextDirection::RTL) => Some(false), 438 | None => None, 439 | }, 440 | Axis::Vertical => match vertical_direction.into() { 441 | Some(VerticalDirection::Down) => Some(true), 442 | Some(VerticalDirection::Up) => Some(false), 443 | None => None, 444 | }, 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/rendering/image.rs: -------------------------------------------------------------------------------- 1 | use crate::gestures::HitTestTarget; 2 | use crate::rendering::{BoxConstraints, Offset, PaintContext, RenderBox, RenderObject, Size}; 3 | use crate::ui::Image; 4 | use std::any::{type_name, TypeId}; 5 | use std::borrow::Borrow; 6 | 7 | #[derive(Debug)] 8 | pub struct RenderImage { 9 | // RenderObject 10 | pub(crate) size: Size, 11 | 12 | // RenderImage 13 | pub(crate) image: Option, 14 | pub(crate) width: Option, 15 | pub(crate) height: Option, 16 | } 17 | 18 | impl Default for RenderImage { 19 | fn default() -> Self { 20 | RenderImage { 21 | image: None, 22 | width: None, 23 | height: None, 24 | size: Size::zero(), 25 | } 26 | } 27 | } 28 | 29 | impl RenderImage { 30 | pub fn new(width: impl Into>, heigh: impl Into>) -> Self { 31 | RenderImage { 32 | image: None, 33 | width: width.into(), 34 | height: heigh.into(), 35 | size: Size::zero(), 36 | } 37 | } 38 | } 39 | 40 | impl From for RenderImage { 41 | fn from(img: Image) -> Self { 42 | let size = Size::new(img.width() as f32, img.height() as f32); 43 | RenderImage { 44 | image: img.into(), 45 | width: None, 46 | height: None, 47 | size, 48 | } 49 | } 50 | } 51 | 52 | impl HitTestTarget for RenderImage {} 53 | 54 | impl RenderObject for RenderImage { 55 | fn ty_id(&self) -> TypeId { 56 | TypeId::of::() 57 | } 58 | 59 | fn ty_name(&self) -> &'static str { 60 | type_name::() 61 | } 62 | 63 | fn paint(&self, context: &mut PaintContext, offset: Offset) { 64 | context.draw_rect(offset, self.size); 65 | } 66 | } 67 | 68 | impl RenderBox for RenderImage { 69 | fn perform_layout(&mut self, constraints: &BoxConstraints) { 70 | let constraints = BoxConstraints::tight_for(self.width, self.height).enforce(constraints); 71 | self.size = match &self.image { 72 | Some(img) => constraints 73 | .borrow() 74 | .constrain_with_aspect_ratio((img.width() as f32, img.height() as f32)), 75 | None => constraints.borrow().smallest(), 76 | }; 77 | } 78 | 79 | fn perform_resize(&mut self, constraints: &BoxConstraints) { 80 | todo!() 81 | } 82 | 83 | fn size(&self) -> Size { 84 | self.size 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/rendering/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_types; 2 | pub use basic_types::*; 3 | 4 | mod object; 5 | pub use object::*; 6 | 7 | mod r#box; 8 | pub use r#box::*; 9 | 10 | mod render_view; 11 | pub use render_view::*; 12 | 13 | mod proxy_box; 14 | pub use proxy_box::*; 15 | 16 | mod image; 17 | pub use image::*; 18 | 19 | mod flex; 20 | pub use flex::*; 21 | 22 | mod sliver; 23 | pub use sliver::*; 24 | 25 | mod sliver_list; 26 | pub use sliver_list::*; 27 | -------------------------------------------------------------------------------- /src/rendering/object.rs: -------------------------------------------------------------------------------- 1 | use std::{any::TypeId, cell::RefCell, rc::Rc}; 2 | 3 | use compose_rt::Composer; 4 | 5 | use crate::{ 6 | gestures::{HitTestResult, HitTestTarget, PointerEvent}, 7 | rendering::{BoxConstraints, Offset, RenderBox, Size}, 8 | widgets::{BuildContext, View, Widget}, 9 | }; 10 | use std::fmt::Debug; 11 | 12 | pub struct PaintContext<'a> { 13 | pub canvas: &'a mut skia_safe::Canvas, 14 | } 15 | 16 | impl<'a> PaintContext<'a> { 17 | pub fn new(canvas: &'a mut skia_safe::Canvas) -> Self { 18 | PaintContext { canvas } 19 | } 20 | 21 | pub fn draw_rect(&mut self, offset: Offset, size: Size) { 22 | let mut paint = skia_safe::Paint::new(skia_safe::Color4f::new(1.0, 1.0, 1.0, 1.0), None); 23 | paint.set_anti_alias(true); 24 | paint.set_style(skia_safe::paint::Style::Stroke); 25 | paint.set_stroke_width(2.0); 26 | 27 | self.canvas.draw_rect( 28 | skia_safe::Rect { 29 | left: offset.x, 30 | top: offset.y, 31 | right: offset.x + size.width, 32 | bottom: offset.y + size.height, 33 | }, 34 | &paint, 35 | ); 36 | } 37 | } 38 | 39 | pub trait RenderObject: Debug + HitTestTarget { 40 | fn ty_id(&self) -> TypeId; 41 | 42 | fn ty_name(&self) -> &'static str; 43 | 44 | fn paint(&self, context: &mut PaintContext, offset: Offset); 45 | 46 | fn hit_test(&self, position: Offset, result: &mut HitTestResult) -> bool { 47 | false 48 | } 49 | } 50 | 51 | pub struct PipelineOwner { 52 | size: Size, 53 | root_fn: Box Rc>>, 54 | render_view: Option>>, 55 | } 56 | 57 | impl PipelineOwner { 58 | pub fn new(size: Size, root: T) -> Self 59 | where 60 | T: 'static + Widget, 61 | { 62 | let view = View::new(root); 63 | let root_fn = Box::new(move |cx: BuildContext| view.create(cx)); 64 | PipelineOwner { 65 | size, 66 | root_fn, 67 | render_view: None, 68 | } 69 | } 70 | 71 | pub fn handle_event(&mut self, position: Offset) { 72 | println!("handle_event {:?}", position); 73 | 74 | if let Some(view) = &mut self.render_view { 75 | let mut result = HitTestResult::new(); 76 | view.borrow().hit_test(position, &mut result); 77 | 78 | println!("hit result len {:?}", result.path.len()); 79 | 80 | for entry in result.path { 81 | // TODO: PointerEvent 82 | entry 83 | .target 84 | .borrow_mut() 85 | .handle_event(PointerEvent::new(position), entry.clone()); 86 | } 87 | } 88 | } 89 | 90 | pub fn draw_frame(&mut self, cx: &mut Composer, context: &mut PaintContext) { 91 | // re-build render tree; 92 | self.render_view = Some((self.root_fn)(cx)); 93 | 94 | //println!("{:#?}", self.render_view); 95 | //println!("{:#?}", self.context); 96 | 97 | self.flush_layout(); 98 | self.flush_paint(context); 99 | } 100 | 101 | pub fn flush_layout(&mut self) { 102 | if let Some(view) = &mut self.render_view { 103 | let constraints = &BoxConstraints::tight(self.size); 104 | view.borrow_mut().layout(constraints, false) 105 | } 106 | } 107 | 108 | pub fn flush_paint(&mut self, context: &mut PaintContext) { 109 | if let Some(view) = &mut self.render_view { 110 | view.borrow_mut().paint(context, Offset::zero()); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/rendering/proxy_box.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::{type_name, TypeId}, 3 | cell::RefCell, 4 | rc::Rc, 5 | }; 6 | 7 | use crate::{ 8 | gestures::HitTestTarget, 9 | rendering::{BoxConstraints, Offset, PaintContext, RenderBox, RenderObject, Size}, 10 | }; 11 | 12 | #[derive(Debug)] 13 | pub struct RenderConstrainedBox { 14 | // RenderObject 15 | pub(crate) size: Size, 16 | 17 | // RenderConstrainedBox 18 | pub(crate) additional_constraints: BoxConstraints, 19 | pub(crate) child: Option>>, 20 | } 21 | 22 | impl RenderConstrainedBox { 23 | pub fn new(constraints: BoxConstraints) -> Self { 24 | RenderConstrainedBox { 25 | size: Size::zero(), 26 | additional_constraints: constraints, 27 | child: None, 28 | } 29 | } 30 | } 31 | 32 | impl HitTestTarget for RenderConstrainedBox {} 33 | 34 | impl RenderObject for RenderConstrainedBox { 35 | fn ty_id(&self) -> TypeId { 36 | TypeId::of::() 37 | } 38 | 39 | fn ty_name(&self) -> &'static str { 40 | type_name::() 41 | } 42 | 43 | fn paint(&self, context: &mut PaintContext, offset: Offset) { 44 | context.draw_rect(offset, self.size); 45 | } 46 | 47 | fn hit_test(&self, position: Offset, result: &mut crate::gestures::HitTestResult) -> bool { 48 | self.size().contains(position) 49 | } 50 | } 51 | 52 | impl RenderBox for RenderConstrainedBox { 53 | fn perform_layout(&mut self, constraints: &BoxConstraints) { 54 | self.size = match &mut self.child { 55 | Some(child) => { 56 | child.borrow_mut().layout(constraints, true); 57 | child.borrow().size() 58 | } 59 | None => self 60 | .additional_constraints 61 | .enforce(constraints) 62 | .constrain(Size::zero()), 63 | }; 64 | } 65 | 66 | fn perform_resize(&mut self, constraints: &BoxConstraints) { 67 | todo!() 68 | } 69 | 70 | fn size(&self) -> Size { 71 | self.size 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/rendering/render_view.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::{type_name, TypeId}, 3 | cell::RefCell, 4 | rc::Rc, 5 | }; 6 | 7 | use crate::{ 8 | gestures::{HitTestEntry, HitTestResult, HitTestTarget}, 9 | rendering::{BoxConstraints, Offset, PaintContext, RenderBox, RenderObject, Size}, 10 | }; 11 | 12 | #[derive(Debug)] 13 | pub struct RenderView { 14 | // RenderObject 15 | pub(crate) size: Size, 16 | 17 | pub(crate) child: Option>>, 18 | } 19 | 20 | impl RenderView { 21 | pub fn new() -> Self { 22 | RenderView { 23 | size: Size::zero(), 24 | child: None, 25 | } 26 | } 27 | } 28 | 29 | impl HitTestTarget for RenderView {} 30 | 31 | impl RenderObject for RenderView { 32 | fn ty_id(&self) -> TypeId { 33 | TypeId::of::() 34 | } 35 | 36 | fn ty_name(&self) -> &'static str { 37 | type_name::() 38 | } 39 | 40 | fn paint(&self, context: &mut PaintContext, offset: Offset) { 41 | if let Some(child) = &self.child { 42 | child.borrow().paint(context, offset); 43 | } 44 | } 45 | 46 | fn hit_test(&self, position: Offset, result: &mut HitTestResult) -> bool { 47 | if let Some(child) = &self.child { 48 | let is_hit = child.borrow().hit_test(position, result); 49 | if is_hit { 50 | let entry = HitTestEntry::new(child.clone()); 51 | result.add(entry); 52 | } 53 | is_hit 54 | } else { 55 | false 56 | } 57 | } 58 | } 59 | 60 | impl RenderBox for RenderView { 61 | fn perform_layout(&mut self, constraints: &BoxConstraints) { 62 | self.size = match &mut self.child { 63 | Some(child) => { 64 | child.borrow_mut().layout(constraints, true); 65 | child.borrow().size() 66 | } 67 | None => constraints.constrain(Size::zero()), 68 | }; 69 | } 70 | 71 | fn perform_resize(&mut self, constraints: &BoxConstraints) { 72 | if let Some(child) = &mut self.child { 73 | child.borrow_mut().perform_resize(constraints); 74 | self.size = child.borrow().size(); 75 | } 76 | } 77 | 78 | fn size(&self) -> Size { 79 | self.size 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/rendering/sliver.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::{ 4 | gestures::HitTestTarget, 5 | rendering::{Axis, AxisDirection, BoxConstraints, RenderBox, RenderObject, ScrollDirection}, 6 | }; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub enum GrowthDirection { 10 | Forward, 11 | Reverse, 12 | } 13 | 14 | #[derive(Debug, Clone, PartialEq)] 15 | pub struct SliverConstraints { 16 | pub axis_direction: AxisDirection, 17 | pub growth_direction: GrowthDirection, 18 | pub scroll_direction: ScrollDirection, 19 | pub scroll_offset: f32, 20 | pub preceding_scroll_extent: f32, 21 | pub overlap: f32, 22 | pub remaining_paint_extent: f32, 23 | pub cross_axis_extent: f32, 24 | pub cross_axis_direction: AxisDirection, 25 | pub viewport_main_axis_extent: f32, 26 | pub remaining_cache_extent: f32, 27 | pub cache_origin: f32, 28 | } 29 | 30 | impl SliverConstraints { 31 | pub fn axis(&self) -> Axis { 32 | self.axis_direction.into() 33 | } 34 | 35 | pub fn calculate_paint_offset(&self, from: f32, to: f32) -> f32 { 36 | debug_assert!(from <= to); 37 | let a = self.scroll_offset; 38 | let b = self.scroll_offset + self.remaining_paint_extent; 39 | (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, self.remaining_paint_extent) 40 | } 41 | 42 | pub fn calculate_cache_offset(&self, from: f32, to: f32) -> f32 { 43 | debug_assert!(from <= to); 44 | let a = self.scroll_offset + self.cache_origin; 45 | let b = self.scroll_offset + self.remaining_cache_extent; 46 | (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, self.remaining_cache_extent) 47 | } 48 | } 49 | 50 | impl<'a> From<&'a SliverConstraints> for BoxConstraints { 51 | fn from(s: &'a SliverConstraints) -> Self { 52 | match s.axis() { 53 | Axis::Horizontal => BoxConstraints { 54 | min_width: 0.0, 55 | max_width: f32::INFINITY, 56 | min_height: s.cross_axis_extent, 57 | max_height: s.cross_axis_extent, 58 | }, 59 | Axis::Vertical => BoxConstraints { 60 | min_width: s.cross_axis_extent, 61 | max_width: s.cross_axis_extent, 62 | min_height: 0.0, 63 | max_height: f32::INFINITY, 64 | }, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug, Clone, PartialEq, Default)] 70 | pub struct SliverGeometry { 71 | pub scroll_extent: f32, 72 | pub paint_origin: f32, 73 | pub paint_extent: f32, 74 | pub layout_extent: f32, 75 | pub max_paint_extent: f32, 76 | pub max_scroll_obstruction_extent: f32, 77 | pub visible: bool, 78 | pub has_visual_overflow: bool, 79 | pub cache_extent: f32, 80 | pub hit_test_extent: f32, 81 | } 82 | 83 | pub trait RenderSliver: RenderObject { 84 | fn layout(&mut self, constraints: &SliverConstraints) { 85 | self.perform_layout(constraints) 86 | } 87 | 88 | fn perform_layout(&mut self, constraints: &SliverConstraints); 89 | 90 | fn perform_resize(&mut self, constraints: &SliverConstraints); 91 | 92 | fn geometry(&self) -> &SliverGeometry; 93 | } 94 | 95 | #[derive(Debug)] 96 | pub struct RenderSliverToBoxAdapter { 97 | geometry: SliverGeometry, 98 | child: Rc>, 99 | } 100 | 101 | impl HitTestTarget for RenderSliverToBoxAdapter {} 102 | 103 | impl RenderObject for RenderSliverToBoxAdapter { 104 | fn ty_id(&self) -> std::any::TypeId { 105 | todo!() 106 | } 107 | 108 | fn ty_name(&self) -> &'static str { 109 | todo!() 110 | } 111 | 112 | fn paint(&self, context: &mut super::PaintContext, offset: super::Offset) { 113 | todo!() 114 | } 115 | } 116 | 117 | impl RenderSliver for RenderSliverToBoxAdapter { 118 | fn perform_layout(&mut self, constraints: &SliverConstraints) { 119 | let box_constraints = &constraints.into(); 120 | self.child.borrow_mut().layout(box_constraints, true); 121 | 122 | let child_size = self.child.borrow().size(); 123 | let child_extent = match constraints.axis() { 124 | Axis::Horizontal => child_size.width, 125 | Axis::Vertical => child_size.height, 126 | }; 127 | 128 | let painted_child_size = constraints.calculate_paint_offset(0.0, child_extent); 129 | let cache_extent = constraints.calculate_cache_offset(0.0, child_extent); 130 | let has_visual_overflow = 131 | child_extent > constraints.remaining_paint_extent || constraints.scroll_offset > 0.0; 132 | 133 | self.geometry = SliverGeometry { 134 | scroll_extent: child_extent, 135 | paint_origin: 0.0, 136 | paint_extent: painted_child_size, 137 | layout_extent: painted_child_size, 138 | max_paint_extent: child_extent, 139 | max_scroll_obstruction_extent: 0.0, 140 | visible: painted_child_size > 0.0, 141 | has_visual_overflow: has_visual_overflow, 142 | cache_extent: cache_extent, 143 | hit_test_extent: painted_child_size, 144 | }; 145 | } 146 | 147 | fn perform_resize(&mut self, constraints: &SliverConstraints) { 148 | todo!() 149 | } 150 | 151 | fn geometry(&self) -> &SliverGeometry { 152 | todo!() 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/rendering/sliver_list.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::{type_name, TypeId}, 3 | cell::RefCell, 4 | rc::Rc, 5 | }; 6 | 7 | use crate::{ 8 | gestures::HitTestTarget, 9 | rendering::{ 10 | Axis, AxisDirection, BoxConstraints, GrowthDirection, Offset, RenderBox, RenderObject, 11 | RenderSliver, ScrollDirection, Size, SliverConstraints, 12 | }, 13 | }; 14 | 15 | #[derive(Debug)] 16 | pub struct RenderSliverList { 17 | // RenderBox 18 | pub(crate) size: Size, 19 | 20 | // RenderSliverList 21 | pub(crate) direction: Axis, 22 | pub(crate) cache_extent: f32, 23 | pub(crate) children: Vec>>, 24 | } 25 | 26 | impl RenderSliverList { 27 | pub fn new(direction: Axis, cache_extent: f32) -> Self { 28 | RenderSliverList { 29 | size: Size::zero(), 30 | direction, 31 | cache_extent, 32 | children: Vec::new(), 33 | } 34 | } 35 | 36 | pub fn with_child(mut self, child: Rc>) -> Self { 37 | self.children.push(child); 38 | self 39 | } 40 | } 41 | 42 | impl HitTestTarget for RenderSliverList {} 43 | 44 | impl RenderObject for RenderSliverList { 45 | fn ty_id(&self) -> std::any::TypeId { 46 | TypeId::of::() 47 | } 48 | 49 | fn ty_name(&self) -> &'static str { 50 | type_name::() 51 | } 52 | 53 | fn paint(&self, context: &mut super::PaintContext, offset: Offset) { 54 | todo!() 55 | } 56 | } 57 | 58 | impl RenderBox for RenderSliverList { 59 | fn size(&self) -> Size { 60 | self.size 61 | } 62 | 63 | fn perform_layout(&mut self, constraints: &BoxConstraints) { 64 | self.size = constraints.biggest(); 65 | if self.size.is_empty() { 66 | return; 67 | } 68 | 69 | // if first layout 70 | let sliver_constraints = match self.direction { 71 | Axis::Horizontal => SliverConstraints { 72 | axis_direction: AxisDirection::Left, 73 | growth_direction: GrowthDirection::Forward, 74 | scroll_direction: ScrollDirection::Idle, 75 | scroll_offset: 0.0, 76 | preceding_scroll_extent: 0.0, 77 | overlap: 0.0, 78 | remaining_paint_extent: self.size.width, 79 | cross_axis_extent: self.size.height, 80 | cross_axis_direction: AxisDirection::Down, 81 | viewport_main_axis_extent: self.size.width, 82 | remaining_cache_extent: self.cache_extent, 83 | cache_origin: 0.0, 84 | }, 85 | Axis::Vertical => SliverConstraints { 86 | axis_direction: AxisDirection::Down, 87 | growth_direction: GrowthDirection::Forward, 88 | scroll_direction: ScrollDirection::Idle, 89 | scroll_offset: 0.0, 90 | preceding_scroll_extent: 0.0, 91 | overlap: 0.0, 92 | remaining_paint_extent: self.size.height, 93 | cross_axis_extent: self.size.width, 94 | cross_axis_direction: AxisDirection::Right, 95 | viewport_main_axis_extent: self.size.height, 96 | remaining_cache_extent: self.cache_extent, 97 | cache_origin: 0.0, 98 | }, 99 | }; 100 | 101 | for child in self.children.iter_mut() { 102 | child.borrow_mut().layout(&sliver_constraints); 103 | } 104 | } 105 | 106 | fn perform_resize(&mut self, constraints: &BoxConstraints) { 107 | todo!() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/tests/common/box.rs: -------------------------------------------------------------------------------- 1 | use std::any::{type_name, TypeId}; 2 | 3 | use crate::rendering::{BoxConstraints, RenderBox, RenderObject, Size}; 4 | 5 | #[derive(Debug)] 6 | pub struct RenderSizedBox { 7 | pub(crate) size: Size, 8 | } 9 | 10 | impl RenderSizedBox { 11 | pub fn new(width: f32, height: f32) -> Self { 12 | RenderSizedBox { 13 | size: Size::new(width, height), 14 | } 15 | } 16 | } 17 | 18 | impl RenderObject for RenderSizedBox { 19 | fn ty_id(&self) -> std::any::TypeId { 20 | TypeId::of::() 21 | } 22 | 23 | fn ty_name(&self) -> &'static str { 24 | type_name::() 25 | } 26 | } 27 | 28 | impl RenderBox for RenderSizedBox { 29 | fn perform_layout(&mut self, constraints: &BoxConstraints) {} 30 | 31 | fn perform_resize(&mut self, constraints: &BoxConstraints) { 32 | todo!() 33 | } 34 | 35 | fn size(&self) -> Size { 36 | self.size 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tests/common/image.rs: -------------------------------------------------------------------------------- 1 | use crate::{rendering::RenderImage, ui::Image}; 2 | use skia_safe::Surface; 3 | 4 | pub fn create_image(width: i32, height: i32) -> RenderImage { 5 | let mut surface = Surface::new_raster_n32_premul((width, height)).unwrap(); 6 | let image = Image { 7 | inner: surface.image_snapshot(), 8 | }; 9 | RenderImage::from(image) 10 | } 11 | -------------------------------------------------------------------------------- /src/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod image; 2 | pub use image::*; 3 | 4 | mod r#box; 5 | pub use r#box::RenderSizedBox; 6 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod rendering; 3 | -------------------------------------------------------------------------------- /src/tests/rendering/flex_test.rs: -------------------------------------------------------------------------------- 1 | use crate::rendering::{ 2 | Axis, BoxConstraints, FlexFit, Flexible, RenderBox, RenderConstrainedBox, RenderFlex, Size, 3 | TextDirection, 4 | }; 5 | 6 | #[test] 7 | fn test_over_constrained() { 8 | let b = RenderConstrainedBox::new(BoxConstraints::tight((10.0, 10.0))); 9 | 10 | let mut flex = RenderFlex::default() 11 | .with_text_direction(TextDirection::LTR) 12 | .with_child(b); 13 | 14 | flex.layout( 15 | &BoxConstraints { 16 | min_width: 200.0, 17 | max_width: 200.0, 18 | min_height: 200.0, 19 | max_height: 200.0, 20 | }, 21 | false, 22 | ); 23 | assert_eq!(flex.size(), Size::new(200.0, 200.0)); 24 | } 25 | 26 | #[test] 27 | fn test_vertical_overflow() { 28 | let mut flex = RenderFlex::default() 29 | .with_direction(Axis::Vertical) 30 | .with_child(RenderConstrainedBox::new(BoxConstraints::tight_for( 31 | None, 200.0, 32 | ))) 33 | .with_child(Flexible::new( 34 | RenderConstrainedBox::new(BoxConstraints::expand(None, None)), 35 | 1, 36 | FlexFit::Loose, 37 | )); 38 | 39 | let viewport = BoxConstraints::default() 40 | .with_max_height(100.0) 41 | .with_max_width(100.0); 42 | 43 | flex.layout(&viewport, false); 44 | 45 | assert_eq!(flex.size(), Size::new(100.0, 100.0)); 46 | assert_eq!(flex.children[1].inner.size(), Size::new(100.0, 0.0)); 47 | } 48 | -------------------------------------------------------------------------------- /src/tests/rendering/image_test.rs: -------------------------------------------------------------------------------- 1 | use crate::rendering::{BoxConstraints, RenderBox, RenderImage, Size}; 2 | use crate::tests::common::create_image; 3 | 4 | #[test] 5 | fn test_square_image() { 6 | let mut image = create_image(10, 10); 7 | 8 | image.layout( 9 | &BoxConstraints { 10 | min_width: 25.0, 11 | min_height: 25.0, 12 | max_width: 100.0, 13 | max_height: 100.0, 14 | }, 15 | false, 16 | ); 17 | assert_eq!(image.size(), Size::new(25.0, 25.0)); 18 | 19 | image.layout( 20 | &BoxConstraints { 21 | min_width: 4.0, 22 | min_height: 4.0, 23 | max_width: 8.0, 24 | max_height: 8.0, 25 | }, 26 | false, 27 | ); 28 | assert_eq!(image.size(), Size::new(8.0, 8.0)); 29 | } 30 | 31 | #[test] 32 | fn test_wide_image() { 33 | let mut image = create_image(20, 10); 34 | 35 | image.layout( 36 | &BoxConstraints { 37 | min_width: 5.0, 38 | min_height: 30.0, 39 | max_width: 100.0, 40 | max_height: 100.0, 41 | }, 42 | false, 43 | ); 44 | assert_eq!(image.size(), Size::new(60.0, 30.0)); 45 | 46 | image.layout( 47 | &BoxConstraints { 48 | min_width: 5.0, 49 | min_height: 5.0, 50 | max_width: 100.0, 51 | max_height: 100.0, 52 | }, 53 | false, 54 | ); 55 | assert_eq!(image.size(), Size::new(20.0, 10.0)); 56 | 57 | image.layout( 58 | &BoxConstraints { 59 | min_width: 20.0, 60 | min_height: 20.0, 61 | max_width: 30.0, 62 | max_height: 30.0, 63 | }, 64 | false, 65 | ); 66 | assert_eq!(image.size(), Size::new(30.0, 20.0)); 67 | } 68 | 69 | #[test] 70 | fn test_tall_image() { 71 | let mut image = create_image(10, 20); 72 | 73 | image.layout( 74 | &BoxConstraints { 75 | min_width: 50.0, 76 | min_height: 5.0, 77 | max_width: 75.0, 78 | max_height: 75.0, 79 | }, 80 | false, 81 | ); 82 | assert_eq!(image.size(), Size::new(50.0, 75.0)); 83 | 84 | image.layout( 85 | &BoxConstraints { 86 | min_width: 5.0, 87 | min_height: 5.0, 88 | max_width: 16.0, 89 | max_height: 16.0, 90 | }, 91 | false, 92 | ); 93 | assert_eq!(image.size(), Size::new(8.0, 16.0)); 94 | 95 | image.layout( 96 | &BoxConstraints { 97 | min_width: 20.0, 98 | min_height: 20.0, 99 | max_width: 30.0, 100 | max_height: 30.0, 101 | }, 102 | false, 103 | ); 104 | assert_eq!(image.size(), Size::new(20.0, 30.0)); 105 | } 106 | 107 | #[test] 108 | fn test_none_image() { 109 | let mut image = RenderImage::default(); 110 | image.layout( 111 | &BoxConstraints { 112 | min_width: 25.0, 113 | min_height: 25.0, 114 | max_width: 100.0, 115 | max_height: 100.0, 116 | }, 117 | false, 118 | ); 119 | assert_eq!(image.size(), Size::new(25.0, 25.0)); 120 | 121 | let mut image = RenderImage::new(50.0, None); 122 | image.layout( 123 | &BoxConstraints { 124 | min_width: 25.0, 125 | min_height: 25.0, 126 | max_width: 100.0, 127 | max_height: 100.0, 128 | }, 129 | false, 130 | ); 131 | assert_eq!(image.size(), Size::new(50.0, 25.0)); 132 | 133 | let mut image = RenderImage::new(None, 50.0); 134 | image.layout( 135 | &BoxConstraints { 136 | min_width: 25.0, 137 | min_height: 25.0, 138 | max_width: 100.0, 139 | max_height: 100.0, 140 | }, 141 | false, 142 | ); 143 | assert_eq!(image.size(), Size::new(25.0, 50.0)); 144 | 145 | let mut image = RenderImage::new(100.0, 100.0); 146 | image.layout( 147 | &BoxConstraints { 148 | min_width: 25.0, 149 | min_height: 25.0, 150 | max_width: 75.0, 151 | max_height: 75.0, 152 | }, 153 | false, 154 | ); 155 | assert_eq!(image.size(), Size::new(75.0, 75.0)); 156 | } 157 | -------------------------------------------------------------------------------- /src/tests/rendering/mod.rs: -------------------------------------------------------------------------------- 1 | mod flex_test; 2 | mod image_test; 3 | mod slivers_test; 4 | -------------------------------------------------------------------------------- /src/tests/rendering/slivers_test.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | rendering::{Axis, RenderBox, RenderSliverList}, 3 | tests::common::RenderSizedBox, 4 | }; 5 | 6 | #[test] 7 | fn test_sliver_list() { 8 | let list = RenderSliverList::new(Axis::Vertical, 200.0) 9 | .with_child(RenderSizedBox::new(100.0, 400.0).into_sliver()) 10 | .with_child(RenderSizedBox::new(100.0, 400.0).into_sliver()) 11 | .with_child(RenderSizedBox::new(100.0, 400.0).into_sliver()) 12 | .with_child(RenderSizedBox::new(100.0, 400.0).into_sliver()) 13 | .with_child(RenderSizedBox::new(100.0, 400.0).into_sliver()); 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/app.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cksac/oxui/8aaa6bbe30f2481954dcd9f2f30308be7a4f8947/src/ui/app.rs -------------------------------------------------------------------------------- /src/ui/image.rs: -------------------------------------------------------------------------------- 1 | use skia_safe::image::Image as SkImage; 2 | 3 | #[derive(Debug)] 4 | pub struct Image { 5 | pub(crate) inner: SkImage, 6 | } 7 | 8 | impl Image { 9 | pub fn width(&self) -> i32 { 10 | self.inner.width() 11 | } 12 | 13 | pub fn height(&self) -> i32 { 14 | self.inner.height() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | mod image; 2 | pub use image::*; 3 | -------------------------------------------------------------------------------- /src/widgets/constrained_box.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::{ 4 | rendering::{BoxConstraints, RenderBox, RenderConstrainedBox}, 5 | widgets::{BuildContext, Widget}, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct ConstrainedBox { 10 | pub constraints: BoxConstraints, 11 | } 12 | 13 | impl Default for ConstrainedBox { 14 | fn default() -> Self { 15 | Self { 16 | constraints: BoxConstraints::expand(), 17 | } 18 | } 19 | } 20 | 21 | impl Widget for ConstrainedBox { 22 | #[track_caller] 23 | fn create(&self, context: BuildContext) -> Rc> { 24 | context.memo( 25 | |_| Rc::new(RefCell::new(RenderConstrainedBox::new(self.constraints))), 26 | |n| n.borrow().additional_constraints == self.constraints, 27 | |n| n.borrow_mut().additional_constraints = self.constraints, 28 | |n| n.clone(), 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/widgets/flex.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | use typed_builder::TypedBuilder; 3 | 4 | use crate::{ 5 | rendering::{ 6 | Axis, BoxConstraints, Clip, CrossAxisAlignment, FlexFit, MainAxisAlignment, MainAxisSize, 7 | RenderBox, RenderConstrainedBox, RenderFlex, RenderFlexible, TextBaseline, TextDirection, 8 | VerticalDirection, 9 | }, 10 | widgets::{BuildContext, Widget}, 11 | }; 12 | 13 | #[derive(Debug, TypedBuilder)] 14 | pub struct Flex { 15 | #[builder(default=Axis::Horizontal)] 16 | pub direction: Axis, 17 | 18 | #[builder(default=MainAxisSize::Max)] 19 | pub main_axis_size: MainAxisSize, 20 | 21 | #[builder(default=MainAxisAlignment::Start)] 22 | pub main_axis_alignment: MainAxisAlignment, 23 | 24 | #[builder(default=CrossAxisAlignment::Center)] 25 | pub cross_axis_alignment: CrossAxisAlignment, 26 | 27 | #[builder(default=VerticalDirection::Down)] 28 | pub vertical_direction: VerticalDirection, 29 | 30 | #[builder(default=Some(TextDirection::LTR), setter(strip_option))] 31 | pub text_direction: Option, 32 | 33 | #[builder(default, setter(strip_option))] 34 | pub text_baseline: Option, 35 | 36 | #[builder(default=Clip::None)] 37 | pub clip_behavior: Clip, 38 | 39 | pub children: Vec, 40 | } 41 | 42 | impl Widget for Flex { 43 | #[track_caller] 44 | fn create(&self, context: BuildContext) -> Rc> { 45 | context.group( 46 | |_| { 47 | let flex = RenderFlex::default().with_direction(self.direction); 48 | Rc::new(RefCell::new(flex)) 49 | }, 50 | |_| { 51 | // TODO: condition to skip whole Flex? 52 | false 53 | }, 54 | |cx| { 55 | let mut children = Vec::with_capacity(self.children.len()); 56 | for child in self.children.iter() { 57 | children.push(child.create(cx)); 58 | } 59 | children 60 | }, 61 | |n, children| { 62 | let mut flex = n.borrow_mut(); 63 | flex.children.clear(); 64 | flex.children = children; 65 | }, 66 | |n| n.clone(), 67 | ) 68 | } 69 | } 70 | 71 | #[derive(Debug, TypedBuilder)] 72 | pub struct Flexible { 73 | pub flex: usize, 74 | pub fit: FlexFit, 75 | pub child: Box, 76 | } 77 | 78 | impl Flexible { 79 | #[track_caller] 80 | fn create(&self, context: BuildContext) -> RenderFlexible { 81 | context.group( 82 | |cx| { 83 | // temp set child to RenderConstrainedBox first 84 | RenderFlexible::new( 85 | RenderConstrainedBox::new(BoxConstraints::default()), 86 | self.flex, 87 | self.fit, 88 | ) 89 | }, 90 | |n| false, 91 | |cx| self.child.create(cx), 92 | |n, child| { 93 | n.flex = self.flex; 94 | n.fit = self.fit; 95 | n.inner = child 96 | }, 97 | |n| n.clone(), 98 | ) 99 | } 100 | } 101 | 102 | impl From for Flexible 103 | where 104 | T: Widget + 'static, 105 | { 106 | fn from(w: T) -> Self { 107 | Self { 108 | flex: 1, 109 | fit: FlexFit::Loose, 110 | child: Box::new(w), 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | mod widget; 2 | pub use widget::*; 3 | 4 | mod view; 5 | pub use view::*; 6 | 7 | mod flex; 8 | pub use flex::*; 9 | 10 | mod constrained_box; 11 | pub use constrained_box::*; 12 | -------------------------------------------------------------------------------- /src/widgets/view.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::{ 4 | rendering::{RenderBox, RenderView}, 5 | widgets::{BuildContext, Widget}, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct View { 10 | pub child: Box, 11 | } 12 | impl View { 13 | pub fn new(child: T) -> Self 14 | where 15 | T: 'static + Widget, 16 | { 17 | View { 18 | child: Box::new(child), 19 | } 20 | } 21 | } 22 | 23 | impl Widget for View { 24 | #[track_caller] 25 | fn create(&self, context: BuildContext) -> Rc> { 26 | context.group( 27 | |_| Rc::new(RefCell::new(RenderView::new())), 28 | |_| false, 29 | |cx| self.child.create(cx), 30 | |n, child| { 31 | n.borrow_mut().child = Some(child); 32 | }, 33 | |n| n.clone(), 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/widgets/widget.rs: -------------------------------------------------------------------------------- 1 | use compose_rt::Composer; 2 | 3 | use crate::{ 4 | rendering::{FlexFit, RenderBox, RenderSliver}, 5 | widgets::Flexible, 6 | }; 7 | use std::{cell::RefCell, fmt::Debug, rc::Rc}; 8 | 9 | // pub struct BuildContext<'a> { 10 | // pub cx: &'a mut Composer, 11 | // } 12 | 13 | pub type BuildContext<'a> = &'a mut Composer; 14 | 15 | pub trait Widget: Debug { 16 | #[track_caller] 17 | fn create(&self, context: BuildContext) -> Rc>; 18 | 19 | fn into_flexible(self, flex: usize, fit: FlexFit) -> Flexible 20 | where 21 | Self: 'static + Sized, 22 | { 23 | Flexible { 24 | flex, 25 | fit, 26 | child: Box::new(self), 27 | } 28 | } 29 | } 30 | 31 | pub trait SliverWidget: Debug { 32 | fn create(&self, context: &BuildContext) -> Rc>; 33 | } 34 | --------------------------------------------------------------------------------