diff --git a/.vscode/launch.json b/.vscode/launch.json index 5b94a8d7..a6a3713f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,42 @@ "args": [], "cwd": "${workspaceFolder}" }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'slidebar'", + "cargo": { + "args": [ + "build", + "--bin=slidebar", + "--package=slidebar" + ], + "filter": { + "name": "slidebar", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'segmented_button'", + "cargo": { + "args": [ + "build", + "--bin=segmented_button", + "--package=segmented_button" + ], + "filter": { + "name": "segmented_button", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, { "type": "lldb", "request": "launch", diff --git a/Cargo.toml b/Cargo.toml index bcbb6ce3..2660e63a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,18 +17,17 @@ categories = ["gui"] [features] badge = [] card = [] -date_picker = ["chrono", "once_cell", "icon_text"] -color_picker = ["icon_text", "iced_widget/canvas"] +date_picker = ["chrono", "once_cell", "icons"] +color_picker = ["icons", "iced_widget/canvas"] cupertino = ["iced_widget/canvas", "time"] floating_element = [] grid = ["itertools"] glow = [] # TODO -icon_text = ["icons"] icons = [] modal = [] tab_bar = [] tabs = ["tab_bar"] -time_picker = ["chrono", "icon_text", "iced_widget/canvas"] +time_picker = ["chrono", "icons", "iced_widget/canvas"] wrap = [] number_input = ["num-traits"] selection_list = [] @@ -46,7 +45,6 @@ default = [ "date_picker", "color_picker", "floating_element", - "icon_text", "grid", "modal", "tab_bar", @@ -55,12 +53,13 @@ default = [ "wrap", "selection_list", "split", - "menu", + #"menu", "quad", "context_menu", "spinner", "cupertino", "segmented_button", + "num-traits" ] [dependencies] @@ -73,9 +72,15 @@ itertools = { version = "0.11.0", optional = true } [dependencies.iced_widget] +git = "https://github.com/iced-rs/iced.git" +#rev = "b474a2b7a763dcde6a377cb409001a7b5285ee8d" +#version = "0.1.1" + +#[dependencies.iced_renderer] #git = "https://github.com/iced-rs/iced.git" -#rev = "8221794" -version = "0.1.1" +#rev = "751ea77c29f6eb3d00f45f0a04c833a1d03a425c" +#features = ["wgpu"] +#version = "0.1.1" [profile.dev.package."*"] opt-level = 2 @@ -85,7 +90,6 @@ members = [ "examples/badge", "examples/card", "examples/color_picker", - "examples/cupertino/cupertino_alert", "examples/cupertino/cupertino_button", "examples/cupertino/cupertino_spinner", "examples/cupertino/cupertino_switch", @@ -104,19 +108,21 @@ members = [ "examples/selection_list", "examples/split", "examples/split_scroller", - "examples/menu", + #"examples/menu", "examples/spinner", "examples/context_menu", - "examples/WidgetIDReturn", + #"examples/WidgetIDReturn", "examples/segmented_button", + "examples/sliderbar", ] [workspace.dependencies.iced] -#git = "https://github.com/iced-rs/iced.git" -#rev = "8221794" -version = "0.10.0" +git = "https://github.com/iced-rs/iced.git" +#rev = "b474a2b7a763dcde6a377cb409001a7b5285ee8d" +#version = "0.10.0" features = ["advanced", "lazy", "tokio"] [workspace.dependencies.iced_aw] path = "./" default-features = false +features = ["num-traits"] \ No newline at end of file diff --git a/examples/cupertino/cupertino_switch/src/main.rs b/examples/cupertino/cupertino_switch/src/main.rs index b5afa204..4b901c5d 100644 --- a/examples/cupertino/cupertino_switch/src/main.rs +++ b/examples/cupertino/cupertino_switch/src/main.rs @@ -49,10 +49,16 @@ impl Application for Switch { fn view(&self) -> Element { let toggle_1: CupertinoSwitch = CupertinoSwitch::new().on_changed(Some(Box::new( Message::LeftSwitchChanged - ))); + ))).value(match self { + Switch::LeftSwitchChanged(v) => *v, + _ => true, + }); let toggle_2: CupertinoSwitch = CupertinoSwitch::new() - .value(false) + .value(match self { + Switch::LeftSwitchChanged(v) => *v, + _ => false, + }) .on_changed(Some(Box::new(Message::RightSwitchChanged))); let left_text: String = match self { diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml index a612f3a4..562ae245 100644 --- a/examples/menu/Cargo.toml +++ b/examples/menu/Cargo.toml @@ -8,6 +8,6 @@ publish = false iced_aw = { workspace = true, features = [ "menu", "quad", - "icon_text" + "icons" ] } iced = {workspace = true, features = ["svg"]} \ No newline at end of file diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 025fca0c..bcf0fc05 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -151,7 +151,7 @@ impl Application for ModalExample { modal(underlay, overlay) .backdrop(Message::CloseModal) .on_esc(Message::CloseModal) - .align_y(alignment::Vertical::Top) + .align_y(alignment::Vertical::Center) .into() } } diff --git a/examples/multiple_modals/src/main.rs b/examples/multiple_modals/src/main.rs index faedecc7..c7287bf8 100644 --- a/examples/multiple_modals/src/main.rs +++ b/examples/multiple_modals/src/main.rs @@ -6,7 +6,10 @@ use iced_aw::{card, modal, CardStyles}; fn main() -> iced::Result { MultipleModalsExample::run(Settings { window: window::Settings { - size: (500, 150), + size: iced::Size { + width: 500.0, + height: 150.0, + }, position: window::Position::Centered, ..Default::default() }, @@ -114,7 +117,7 @@ impl Application for MultipleModalsExample { self.state = State::Button1; Command::none() } - Message::ButtonQuitPressed => window::close(), + Message::ButtonQuitPressed => window::close(window::Id::MAIN), Message::CloseOverlay => { match (&self.state, &self.button_pressed) { (State::Button1, Some(ButtonPressed::Correct)) => self.state = State::Button2, diff --git a/examples/number_input/src/main.rs b/examples/number_input/src/main.rs index c94ef7a4..7c124a4d 100644 --- a/examples/number_input/src/main.rs +++ b/examples/number_input/src/main.rs @@ -26,9 +26,12 @@ pub enum Message { fn main() -> iced::Result { NumberInputDemo::run(Settings { - default_text_size: 12.0, + default_text_size: iced::Pixels(12.0), window: window::Settings { - size: (250, 200), + size: iced::Size { + width: 250.0, + height: 200.0, + }, ..Default::default() }, ..Settings::default() diff --git a/examples/selection_list/src/main.rs b/examples/selection_list/src/main.rs index 11fc0bbf..cb4e11af 100644 --- a/examples/selection_list/src/main.rs +++ b/examples/selection_list/src/main.rs @@ -61,10 +61,10 @@ impl Sandbox for Example { self.manual_select = None; } Message::ManualSelection => { - if let Some(option) = self.vec.get(0) { + if let Some(option) = self.vec.get(2) { self.selected_language = option.to_owned(); - self.selected_index = 0; - self.manual_select = Some(0); + self.selected_index = 2; + self.manual_select = Some(2); } } } diff --git a/examples/sliderbar/Cargo.toml b/examples/sliderbar/Cargo.toml new file mode 100644 index 00000000..22276daa --- /dev/null +++ b/examples/sliderbar/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "slidebar" +version = "0.1.0" +authors = ["Andrew Wheeler "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced_aw.workspace = true +iced.workspace = true diff --git a/examples/sliderbar/src/main.rs b/examples/sliderbar/src/main.rs new file mode 100644 index 00000000..0b8bb667 --- /dev/null +++ b/examples/sliderbar/src/main.rs @@ -0,0 +1,57 @@ +use iced::{ + widget::{Column, Container, Text}, + Element, Length, Sandbox, Settings, +}; + +use iced_aw::SlideBar; + +fn main() -> iced::Result { + SlideBarExample::run(Settings::default()) +} + +#[derive(Debug, Clone)] +enum Message { + SliderBarChange(u32), +} + +struct SlideBarExample { + value: u32, +} + +impl Sandbox for SlideBarExample { + type Message = Message; + + fn new() -> Self { + SlideBarExample { value: 1 } + } + + fn title(&self) -> String { + String::from("Slider Bar example") + } + + fn update(&mut self, message: Message) { + let Message::SliderBarChange(v) = message; + self.value = v; + } + + fn view(&self) -> Element { + let bar = SlideBar::new(0..=100, self.value, Message::SliderBarChange).width(100.0); + + let content_all = Column::new() + .spacing(10) + .push( + Text::new(format!("Value is {}", self.value)) + .width(Length::Fill) + .vertical_alignment(iced::alignment::Vertical::Center).horizontal_alignment(iced::alignment::Horizontal::Center), + ) + .push(bar) + .align_items(iced::Alignment::Center); + + Container::new(content_all) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/tab_bar/src/main.rs b/examples/tab_bar/src/main.rs index 97b50f3f..4fb4e348 100644 --- a/examples/tab_bar/src/main.rs +++ b/examples/tab_bar/src/main.rs @@ -6,7 +6,10 @@ use iced::{ use iced_aw::{TabBar, TabLabel}; fn main() -> iced::Result { - TabBarExample::run(Settings::default()) + TabBarExample::run(Settings { + default_text_size: 12.into(), + ..Default::default() + }) } #[derive(Debug, Clone)] @@ -131,13 +134,13 @@ impl Application for TabBarExample { .push( TextInput::new("Tab label", &state.new_tab_label) .on_input(Message::TabLabelInputChanged) - .size(22) + .size(16) .padding(5.0), ) .push( TextInput::new("Tab content", &state.new_tab_content) .on_input(Message::TabContentInputChanged) - .size(22) + .size(12) .padding(5.0), ) .push(Button::new(Text::new("New")).on_press(Message::NewTab)) diff --git a/src/lib.rs b/src/lib.rs index 1c70d7e9..eb08bdc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,6 +151,8 @@ mod platform { #[cfg(feature = "spinner")] pub use {crate::native::spinner, crate::style::SpinnerStyle, spinner::Spinner}; + pub use crate::native::SlideBar; + #[doc(no_inline)] #[cfg(feature = "context_menu")] pub use { diff --git a/src/native/badge.rs b/src/native/badge.rs index 0873af76..7af5e2ab 100644 --- a/src/native/badge.rs +++ b/src/native/badge.rs @@ -139,7 +139,7 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let padding = self.padding.into(); let limits = limits .loose() @@ -147,7 +147,10 @@ where .height(self.height) .pad(padding); - let mut content = self.content.as_widget().layout(renderer, &limits.loose()); + let mut content = + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, &limits.loose()); let size = limits.resolve(content.size()); content.move_to(Point::new(padding.left, padding.top)); diff --git a/src/native/card.rs b/src/native/card.rs index 83ab0b94..62fe95b2 100644 --- a/src/native/card.rs +++ b/src/native/card.rs @@ -16,8 +16,8 @@ use iced_widget::{ mouse::{self, Cursor}, renderer, touch, widget::{Operation, Tree}, - Alignment, BorderRadius, Clipboard, Color, Element, Event, Layout, Length, Padding, Point, - Rectangle, Shell, Size, Widget, + Alignment, BorderRadius, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, + Point, Rectangle, Shell, Size, Widget, }, text::LineHeight, }; @@ -244,7 +244,7 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let limits = limits.max_width(self.max_width).max_height(self.max_height); let head_node = head_node( @@ -255,9 +255,17 @@ where self.width, self.on_close.is_some(), self.close_size, + tree, ); - let mut body_node = body_node(renderer, &limits, &self.body, self.padding_body, self.width); + let mut body_node = body_node( + renderer, + &limits, + &self.body, + self.padding_body, + self.width, + tree, + ); body_node.move_to(Point::new( body_node.bounds().x, @@ -265,7 +273,7 @@ where )); let mut foot_node = self.foot.as_ref().map_or_else(Node::default, |foot| { - foot_node(renderer, &limits, foot, self.padding_foot, self.width) + foot_node(renderer, &limits, foot, self.padding_foot, self.width, tree) }); foot_node.move_to(Point::new( @@ -590,6 +598,7 @@ where } /// Calculates the layout of the head. +#[allow(clippy::too_many_arguments)] fn head_node( renderer: &Renderer, limits: &Limits, @@ -598,6 +607,7 @@ fn head_node( width: Length, on_close: bool, close_size: Option, + tree: &mut Tree, ) -> Node where Renderer: core::Renderer + core::text::Renderer, @@ -609,7 +619,7 @@ where .height(head.as_widget().height()) .pad(pad); - let close_size = close_size.unwrap_or_else(|| renderer.default_size()); + let close_size = close_size.unwrap_or_else(|| renderer.default_size().0); let mut close = if on_close { limits = limits.shrink(Size::new(close_size, 0.0)); Some(Node::new(Size::new(close_size + 1.0, close_size + 1.0))) @@ -617,7 +627,9 @@ where None }; - let mut head = head.as_widget().layout(renderer, &limits); + let mut head = head + .as_widget() + .layout(&mut tree.children[0], renderer, &limits); let mut size = limits.resolve(head.size()); head.move_to(Point::new(padding, padding)); @@ -646,6 +658,7 @@ fn body_node( body: &Element<'_, Message, Renderer>, padding: f32, width: Length, + tree: &mut Tree, ) -> Node where Renderer: core::Renderer, @@ -658,7 +671,9 @@ where .height(body.as_widget().height()) .pad(pad); - let mut body = body.as_widget().layout(renderer, &limits); + let mut body = body + .as_widget() + .layout(&mut tree.children[1], renderer, &limits); let size = limits.resolve(body.size()); body.move_to(Point::new(padding, padding)); @@ -674,6 +689,7 @@ fn foot_node( foot: &Element<'_, Message, Renderer>, padding: f32, width: Length, + tree: &mut Tree, ) -> Node where Renderer: core::Renderer, @@ -686,7 +702,9 @@ where .height(foot.as_widget().height()) .pad(pad); - let mut foot = foot.as_widget().layout(renderer, &limits); + let mut foot = foot + .as_widget() + .layout(&mut tree.children[2], renderer, &limits); let size = limits.resolve(foot.size()); foot.move_to(Point::new(padding, padding)); @@ -761,23 +779,25 @@ fn draw_head( let close_bounds = close_layout.bounds(); let is_mouse_over_close = close_bounds.contains(cursor.position().unwrap_or_default()); - renderer.fill_text(core::text::Text { - content: &icon_to_string(BootstrapIcon::X), - bounds: Rectangle { - x: close_bounds.center_x(), - y: close_bounds.center_y(), - height: close_bounds.height, - ..close_bounds + + renderer.fill_text( + core::text::Text { + content: &Icon::X.to_string(), + bounds: Size::new(close_bounds.width, close_bounds.height), + size: Pixels( + close_size.unwrap_or_else(|| renderer.default_size().0) + + if is_mouse_over_close { 1.0 } else { 0.0 }, + ), + font: ICON_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: close_size.unwrap_or_else(|| renderer.default_size()) - + if is_mouse_over_close { 1.0 } else { 0.0 }, - color: style_sheet.close_color, - font: BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(close_bounds.center_x(), close_bounds.center_y()), + style_sheet.close_color, + close_bounds, + ); } } diff --git a/src/native/color_picker.rs b/src/native/color_picker.rs index db5facf6..4dba9c88 100644 --- a/src/native/color_picker.rs +++ b/src/native/color_picker.rs @@ -174,8 +174,8 @@ where self.underlay.as_widget().width() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.underlay.as_widget().layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.underlay.as_widget().layout(&mut tree.children[0], renderer, limits) } fn on_event( diff --git a/src/native/common.rs b/src/native/common.rs new file mode 100644 index 00000000..9c930bb9 --- /dev/null +++ b/src/native/common.rs @@ -0,0 +1,62 @@ +//! Common types for reuse. +//! + +use iced_widget::core::{Padding, Rectangle}; + +/// Methods for creating inner bounds +#[allow(missing_debug_implementations)] +pub enum InnerBounds { + /// Create inner bounds ratio to the outer bounds + Ratio(f32, f32), + /// Create inner bounds by padding the outer bounds + Padding(Padding), + /// Create square inner bounds + Square(f32), + /// Create inner bounds with a custom function + Custom(Box Rectangle>), +} +impl InnerBounds { + /// Gets the inner bounds of the Set type. + pub fn get_bounds(&self, outer_bounds: Rectangle) -> Rectangle { + use InnerBounds::{Custom, Padding, Ratio, Square}; + match self { + Ratio(w, h) => { + let width = w * outer_bounds.width; + let height = h * outer_bounds.height; + let x = outer_bounds.x + (outer_bounds.width - width) * 0.5; + let y = outer_bounds.y + (outer_bounds.height - height) * 0.5; + Rectangle { + x, + y, + width, + height, + } + } + Padding(p) => { + let x = outer_bounds.x + p.left; + let y = outer_bounds.y + p.top; + let width = outer_bounds.width - p.horizontal(); + let height = outer_bounds.width - p.vertical(); + Rectangle { + x, + y, + width, + height, + } + } + Square(l) => { + let width = *l; + let height = *l; + let x = outer_bounds.x + (outer_bounds.width - width) * 0.5; + let y = outer_bounds.y + (outer_bounds.height - height) * 0.5; + Rectangle { + x, + y, + width, + height, + } + } + Custom(f) => f(outer_bounds), + } + } +} diff --git a/src/native/context_menu.rs b/src/native/context_menu.rs index 7b60d001..93127a74 100644 --- a/src/native/context_menu.rs +++ b/src/native/context_menu.rs @@ -95,8 +95,10 @@ where self.underlay.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.underlay.as_widget().layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.underlay + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn draw( diff --git a/src/native/cupertino/cupertino_alert.rs b/src/native/cupertino/cupertino_alert.rs index c4d791ee..334e102d 100644 --- a/src/native/cupertino/cupertino_alert.rs +++ b/src/native/cupertino/cupertino_alert.rs @@ -267,7 +267,7 @@ where } } - fn layout(&self, _renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { Node::new( limits .width(if self.is_hidden { @@ -284,6 +284,13 @@ where ) } + fn children(&self) -> Vec { + self.actions + .iter() + .map(|action| Tree::new(&action.child)) + .collect() + } + fn draw( &self, state: &Tree, @@ -438,7 +445,7 @@ where }); child_1.draw( - state, + &state.children[0], r, theme, style, @@ -448,7 +455,7 @@ where ); child_2.draw( - state, + &state.children[1], r, theme, style, @@ -477,7 +484,7 @@ where y: center.y - height / 3.0, }); - let title: Element<'a, Message, Renderer> = + /*let title: Element<'a, Message, Renderer> = CupertinoAlert::<'a, Message, Renderer>::text_with_font( Text::new(self.title.clone()).horizontal_alignment(Horizontal::Center), ); @@ -505,7 +512,7 @@ where Layout::new(&content_node), cursor, viewport, - ); + );*/ }; renderer.with_layer(rectangle, draw_element); diff --git a/src/native/cupertino/cupertino_button.rs b/src/native/cupertino/cupertino_button.rs index 9804bf49..d9a8c39e 100644 --- a/src/native/cupertino/cupertino_button.rs +++ b/src/native/cupertino/cupertino_button.rs @@ -127,8 +127,19 @@ where self.body.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - return self.body.as_widget().layout(renderer, limits); + fn children(&self) -> Vec { + vec![Tree::new(&self.body)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.body)); + } + + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + return self + .body + .as_widget() + .layout(&mut tree.children[0], renderer, limits); } fn draw( @@ -189,9 +200,15 @@ where new_style.text_color = secondary_system_fill(); } - self.body - .as_widget() - .draw(state, renderer, theme, new_style, layout, cursor, viewport); + self.body.as_widget().draw( + &state.children[0], + renderer, + theme, + new_style, + layout, + cursor, + viewport, + ); } fn on_event( diff --git a/src/native/cupertino/cupertino_spinner.rs b/src/native/cupertino/cupertino_spinner.rs index b897ae85..408e1788 100644 --- a/src/native/cupertino/cupertino_spinner.rs +++ b/src/native/cupertino/cupertino_spinner.rs @@ -98,7 +98,7 @@ impl Widget> for CupertinoSpinner { self.height } - fn layout(&self, _renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { Node::new( limits .width(self.width) @@ -192,7 +192,7 @@ impl Widget> for CupertinoSpinner { ) -> event::Status { let state: &mut SpinnerState = state.state.downcast_mut::(); - if let Event::Window(window::Event::RedrawRequested(_now)) = &event { + if let Event::Window(_id, window::Event::RedrawRequested(_now)) = &event { // if is_visible(&bounds) { state.now = time::OffsetDateTime::now_local() .unwrap_or_else(|_| time::OffsetDateTime::now_utc()); diff --git a/src/native/cupertino/cupertino_switch.rs b/src/native/cupertino/cupertino_switch.rs index f2cf0bb2..94b2043a 100644 --- a/src/native/cupertino/cupertino_switch.rs +++ b/src/native/cupertino/cupertino_switch.rs @@ -166,7 +166,7 @@ where self.height } - fn layout(&self, _renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { Node::new( limits .width(self.width) @@ -340,7 +340,7 @@ where let state: &mut SwitchState = state.state.downcast_mut::(); match event { - Event::Window(window::Event::RedrawRequested(_now)) => { + Event::Window(_id, window::Event::RedrawRequested(_now)) => { if state.toggle_staged { state.animation_frame += 1; diff --git a/src/native/cupertino/mod.rs b/src/native/cupertino/mod.rs index c138f3c7..674a8a5e 100644 --- a/src/native/cupertino/mod.rs +++ b/src/native/cupertino/mod.rs @@ -1,7 +1,8 @@ /// Use a Cupertino-style alert. /// /// *This API requires the following crate features to be activated: `cupertino`* -pub mod cupertino_alert; +/// Depreciated due to Element loop. +//pub mod cupertino_alert; /// Use a Cupertino-style button. /// diff --git a/src/native/date_picker.rs b/src/native/date_picker.rs index 914a6267..ef53775e 100644 --- a/src/native/date_picker.rs +++ b/src/native/date_picker.rs @@ -181,8 +181,10 @@ where self.underlay.as_widget().width() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.underlay.as_widget().layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.underlay + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn on_event( diff --git a/src/native/floating_element.rs b/src/native/floating_element.rs index 32a83665..128b9dce 100644 --- a/src/native/floating_element.rs +++ b/src/native/floating_element.rs @@ -126,8 +126,10 @@ where self.underlay.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.underlay.as_widget().layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.underlay + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn on_event( diff --git a/src/native/grid/layout.rs b/src/native/grid/layout.rs index 1bde7e27..adbc240e 100644 --- a/src/native/grid/layout.rs +++ b/src/native/grid/layout.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use iced_widget::core::{ alignment::{Horizontal, Vertical}, layout::{Limits, Node}, + widget::Tree, Length, Padding, Pixels, Point, Size, }; use itertools::{Itertools, Position}; @@ -11,6 +12,7 @@ use super::types::GridRow; #[allow(clippy::too_many_arguments)] pub(super) fn layout( + tree: &mut Tree, renderer: &Renderer, limits: &Limits, column_count: usize, @@ -34,7 +36,7 @@ where let mut row_heights = Vec::::with_capacity(row_count); // Measure the minimum row and column size to fit the contents - minimum_row_column_sizes(renderer, &mut column_widths, &mut row_heights, rows); + minimum_row_column_sizes(tree, renderer, &mut column_widths, &mut row_heights, rows); // Adjust for fixed row and column sizes adjust_size_for_fixed_length(&mut column_widths, column_lengths); @@ -62,6 +64,7 @@ where // Lay out the widgets create_grid_layout( + tree, element_count, rows, &row_heights, @@ -77,6 +80,7 @@ where } fn minimum_row_column_sizes( + tree: &mut Tree, renderer: &Renderer, column_widths: &mut Vec, row_heights: &mut Vec, @@ -84,12 +88,20 @@ fn minimum_row_column_sizes( ) where Renderer: iced_widget::core::Renderer, { + let mut children = tree.children.iter_mut(); for row in rows { let mut row_height = 0.0f32; for (col_idx, element) in row.elements.iter().enumerate() { let child_limits = Limits::NONE.width(Length::Shrink).height(Length::Shrink); - let Size { width, height } = element.as_widget().layout(renderer, &child_limits).size(); + let Size { width, height } = element + .as_widget() + .layout( + children.next().expect("grid missing expected child"), + renderer, + &child_limits, + ) + .size(); #[allow(clippy::option_if_let_else)] match column_widths.get_mut(col_idx) { @@ -160,6 +172,7 @@ fn allocate_space(current_sizes: &mut [f32], length_settings: &[Length], availab #[allow(clippy::too_many_arguments)] fn create_grid_layout( + tree: &mut Tree, element_count: usize, rows: &[GridRow<'_, Message, Renderer>], row_heights: &[f32], @@ -177,6 +190,8 @@ where { let mut y = padding.top; let mut nodes = Vec::with_capacity(element_count); + let mut children = tree.children.iter_mut(); + for (row_position, (row, &row_height)) in rows.iter().zip(row_heights).with_position() { let mut x = padding.left; for (col_position, (element, &column_width)) in @@ -189,7 +204,11 @@ where .max_width(column_width) .max_height(row_height); - let mut node = widget.layout(renderer, &widget_limits); + let mut node = widget.layout( + children.next().expect("Grid missing child"), + renderer, + &widget_limits, + ); node.move_to(Point::new(x, y)); node.align( horizontal_alignment.into(), diff --git a/src/native/grid/widget.rs b/src/native/grid/widget.rs index a6fe011d..19fce036 100644 --- a/src/native/grid/widget.rs +++ b/src/native/grid/widget.rs @@ -22,7 +22,7 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { if self.element_count() == 0 { return Node::new(Size::ZERO); } @@ -37,6 +37,7 @@ where ); layout( + tree, renderer, limits, self.column_count(), diff --git a/src/native/mod.rs b/src/native/mod.rs index 306f4b18..776752f2 100644 --- a/src/native/mod.rs +++ b/src/native/mod.rs @@ -1,10 +1,15 @@ //! Stateless, pure widgets for iced //use iced_widget::{renderer, style}; pub mod helpers; +#[allow(unused_imports)] pub use helpers::*; pub mod overlay; +pub mod common; + +pub use common::InnerBounds; + #[cfg(feature = "badge")] pub mod badge; #[cfg(feature = "badge")] @@ -132,3 +137,7 @@ pub mod segmented_button; /// A badge for color highlighting small information. pub type SegmentedButton<'a, Message, Renderer> = segmented_button::SegmentedButton<'a, Message, Renderer>; + +pub mod slide_bar; + +pub use slide_bar::SlideBar; diff --git a/src/native/modal.rs b/src/native/modal.rs index b0bb718f..cb71194f 100644 --- a/src/native/modal.rs +++ b/src/native/modal.rs @@ -155,8 +155,10 @@ where self.underlay.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.underlay.as_widget().layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.underlay + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn on_event( diff --git a/src/native/number_input.rs b/src/native/number_input.rs index a793f798..cd97d136 100644 --- a/src/native/number_input.rs +++ b/src/native/number_input.rs @@ -14,8 +14,8 @@ use iced_widget::{ tree::{State, Tag}, Operation, Tree, }, - Alignment, Background, Clipboard, Color, Element, Event, Layout, Length, Padding, - Rectangle, Shell, Size, Widget, + Alignment, Background, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, + Point, Rectangle, Shell, Size, Widget, }, text, text::LineHeight, @@ -295,37 +295,55 @@ where Length::Shrink } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let padding = Padding::from(self.padding); let limits = limits .width(self.width()) .height(Length::Shrink) .pad(padding); - let content = self.content.layout(renderer, &limits); + let content = self + .content + .layout(&mut tree.children[0], renderer, &limits, None); let limits2 = Limits::new(Size::new(0.0, 0.0), content.size()); - let txt_size = self.size.unwrap_or_else(|| renderer.default_size()); + let txt_size = self.size.unwrap_or_else(|| renderer.default_size().0); let icon_size = txt_size * 2.5 / 4.0; let btn_mod = |c| { - Container::<(), Renderer>::new(Text::new(format!(" {c} ")).size(icon_size)) + Container::::new(Text::new(format!(" {c} ")).size(icon_size)) .center_y() .center_x() }; - let mut modifier = if self.padding < DEFAULT_PADDING { - Row::<(), Renderer>::new() - .spacing(1) - .width(Length::Shrink) - .push(btn_mod('+')) - .push(btn_mod('-')) - .layout(renderer, &limits2.loose()) + + let element = if self.padding < DEFAULT_PADDING { + Element::new( + Row::::new() + .spacing(1) + .width(Length::Shrink) + .push(btn_mod('+')) + .push(btn_mod('-')), + ) + } else { + Element::new( + Column::::new() + .spacing(1) + .width(Length::Shrink) + .push(btn_mod('▲')) + .push(btn_mod('▼')), + ) + }; + + let input_tree = if let Some(child_tree) = tree.children.get_mut(1) { + child_tree.diff(element.as_widget()); + child_tree } else { - Column::<(), Renderer>::new() - .spacing(1) - .width(Length::Shrink) - .push(btn_mod('▲')) - .push(btn_mod('▼')) - .layout(renderer, &limits2.loose()) + let child_tree = Tree::new(element.as_widget()); + tree.children.insert(1, child_tree); + &mut tree.children[1] }; + + let mut modifier = element + .as_widget() + .layout(input_tree, renderer, &limits2.loose()); let intrinsic = Size::new( content.size().width - 1.0, content.size().height.max(modifier.size().height), @@ -424,13 +442,16 @@ where } else { match event { Event::Keyboard(keyboard::Event::CharacterReceived(c)) - if child.state.downcast_mut::().is_focused() + if child + .state + .downcast_mut::>() + .is_focused() && c.is_numeric() => { let mut new_val = self.value.to_string(); match child .state - .downcast_mut::() + .downcast_mut::>() .cursor() .state(&Value::new(&new_val)) { @@ -476,7 +497,10 @@ where } } Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) - if child.state.downcast_mut::().is_focused() => + if child + .state + .downcast_mut::>() + .is_focused() => { match key_code { keyboard::KeyCode::Up => { @@ -494,7 +518,7 @@ where let mut new_val = self.value.to_string(); match child .state - .downcast_mut::() + .downcast_mut::>() .cursor() .state(&Value::new(&new_val)) { @@ -639,7 +663,7 @@ where _style: &renderer::Style, layout: Layout<'_>, cursor: Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let mut children = layout.children(); let content_layout = children.next().expect("fail to get content layout"); @@ -662,6 +686,7 @@ where content_layout, cursor, None, + viewport, ); let is_decrease_disabled = self.value <= self.bounds.0 || self.bounds.0 == self.bounds.1; let is_increase_disabled = self.value >= self.bounds.1 || self.bounds.0 == self.bounds.1; @@ -682,9 +707,9 @@ where theme.active(&self.style) }; - let txt_size = self.size.unwrap_or_else(|| renderer.default_size()); + let txt_size = self.size.unwrap_or_else(|| renderer.default_size().0); - let icon_size = txt_size * 2.5 / 4.0; + let icon_size = Pixels(txt_size * 2.5 / 4.0); // decrease button section renderer.fill_quad( @@ -701,21 +726,23 @@ where let mut buffer = [0; 4]; - renderer.fill_text(core::text::Text { - content: char::from(BootstrapIcon::CaretDownFill).encode_utf8(&mut buffer), - bounds: Rectangle { - x: dec_bounds.center_x(), - y: dec_bounds.center_y(), - ..dec_bounds + + renderer.fill_text( + core::text::Text { + content: char::from(Icon::CaretDownFill).encode_utf8(&mut buffer), + bounds: Size::new(dec_bounds.width, dec_bounds.height), + size: icon_size, + font: ICON_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: icon_size, - color: decrease_btn_style.icon_color, - font: BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(dec_bounds.center_x(), dec_bounds.center_y()), + decrease_btn_style.icon_color, + dec_bounds, + ); + // increase button section renderer.fill_quad( @@ -730,21 +757,21 @@ where .unwrap_or(Background::Color(Color::TRANSPARENT)), ); - renderer.fill_text(core::text::Text { - content: char::from(BootstrapIcon::CaretUpFill).encode_utf8(&mut buffer), - bounds: Rectangle { - x: inc_bounds.center_x(), - y: inc_bounds.center_y(), - ..inc_bounds + renderer.fill_text( + core::text::Text { + content: char::from(Icon::CaretUpFill).encode_utf8(&mut buffer), + bounds: Size::new(inc_bounds.width, inc_bounds.height), + size: icon_size, + font: ICON_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: icon_size, - color: increase_btn_style.icon_color, - font: BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(inc_bounds.center_x(), inc_bounds.center_y()), + increase_btn_style.icon_color, + inc_bounds, + ); } } diff --git a/src/native/overlay/color_picker.rs b/src/native/overlay/color_picker.rs index 64d9fb81..ccfa3804 100644 --- a/src/native/overlay/color_picker.rs +++ b/src/native/overlay/color_picker.rs @@ -544,7 +544,13 @@ where Message: 'static + Clone, Theme: 'a + StyleSheet + button::StyleSheet + widget::text::StyleSheet, { - fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + _translation: Vector, + ) -> Node { let (max_width, max_height) = if bounds.width > bounds.height { (600.0, 300.0) } else { @@ -563,13 +569,13 @@ where .spacing(SPACING) .push(Row::new().width(Length::Fill).height(Length::Fill)) .push(Row::new().width(Length::Fill).height(Length::Fill)) - .layout(renderer, &limits) + .layout(self.tree, renderer, &limits) } else { Column::<(), Renderer>::new() .spacing(SPACING) .push(Row::new().width(Length::Fill).height(Length::Fill)) .push(Row::new().width(Length::Fill).height(Length::Fill)) - .layout(renderer, &limits) + .layout(self.tree, renderer, &limits) }; let mut divider_children = divider.children().iter(); @@ -793,7 +799,7 @@ where .next() .expect("Graphics: Layout should have a cancel button layout for a ColorPicker"); let cancel_mouse_interaction = self.cancel_button.mouse_interaction( - &self.tree.children[0], + &self.tree.children[1], cancel_button_layout, cursor, viewport, @@ -881,7 +887,7 @@ where /// Defines the layout of the 1. block of the color picker containing the HSV part. fn block1_layout<'a, Message, Theme>( - _color_picker: &ColorPickerOverlay<'a, Message, Theme>, + color_picker: &mut ColorPickerOverlay<'a, Message, Theme>, renderer: &Renderer, bounds: Rectangle, _position: Point, @@ -906,7 +912,7 @@ where .width(Length::Fill) .height(Length::FillPortion(1)), ) - .layout(renderer, &block1_limits); + .layout(color_picker.tree, renderer, &block1_limits); block1_node.move_to(Point::new(bounds.x + PADDING, bounds.y + PADDING)); @@ -915,7 +921,7 @@ where /// Defines the layout of the 2. block of the color picker containing the RGBA part, Hex and buttons. fn block2_layout<'a, Message, Theme>( - color_picker: &ColorPickerOverlay<'a, Message, Theme>, + color_picker: &mut ColorPickerOverlay<'a, Message, Theme>, renderer: &Renderer, bounds: Rectangle, _position: Point, @@ -930,21 +936,27 @@ where // Pre-Buttons TODO: get rid of it let cancel_limits = block2_limits; - let cancel_button = color_picker.cancel_button.layout(renderer, &cancel_limits); + let cancel_button = color_picker.cancel_button.layout( + &mut color_picker.tree.children[0], + renderer, + &cancel_limits, + ); let hex_text_limits = block2_limits; - let mut hex_text = Row::<(), Renderer>::new() + + let mut hex_text_layout = Row::>::new() .width(Length::Fill) - .height(Length::Fixed(renderer.default_size() + 2.0 * PADDING)) - .layout(renderer, &hex_text_limits); + .height(Length::Fixed(renderer.default_size().0 + 2.0 * PADDING)) + .layout(color_picker.tree, renderer, &hex_text_limits); let block2_limits = block2_limits.shrink(Size::new( 0.0, - cancel_button.bounds().height + hex_text.bounds().height + 2.0 * SPACING, + cancel_button.bounds().height + hex_text_layout.bounds().height + 2.0 * SPACING, )); // RGBA Colors - let mut rgba_colors = Column::<(), Renderer>::new(); + let mut rgba_colors: Column<'_, Message, Renderer> = + Column::>::new(); for _ in 0..4 { rgba_colors = rgba_colors.push( @@ -970,8 +982,19 @@ where ), ); } + let element: Element> = Element::new(rgba_colors); + let rgba_tree = if let Some(child_tree) = color_picker.tree.children.get_mut(2) { + child_tree.diff(element.as_widget()); + child_tree + } else { + let child_tree = Tree::new(element.as_widget()); + color_picker.tree.children.insert(2, child_tree); + &mut color_picker.tree.children[2] + }; - let mut rgba_colors = rgba_colors.layout(renderer, &block2_limits); + let mut rgba_colors = element + .as_widget() + .layout(rgba_tree, renderer, &block2_limits); rgba_colors.move_to(Point::new( rgba_colors.bounds().x + PADDING, @@ -979,27 +1002,35 @@ where )); // Hex text - hex_text.move_to(Point::new( - hex_text.bounds().x + PADDING, - hex_text.bounds().y + rgba_colors.bounds().height + PADDING + SPACING, + hex_text_layout.move_to(Point::new( + hex_text_layout.bounds().x + PADDING, + hex_text_layout.bounds().y + rgba_colors.bounds().height + PADDING + SPACING, )); // Buttons let cancel_limits = block2_limits.max_width(((rgba_colors.bounds().width / 2.0) - BUTTON_SPACING).max(0.0)); - let mut cancel_button = color_picker.cancel_button.layout(renderer, &cancel_limits); + let mut cancel_button = color_picker.cancel_button.layout( + &mut color_picker.tree.children[0], + renderer, + &cancel_limits, + ); let submit_limits = block2_limits.max_width(((rgba_colors.bounds().width / 2.0) - BUTTON_SPACING).max(0.0)); - let mut submit_button = color_picker.submit_button.layout(renderer, &submit_limits); + let mut submit_button = color_picker.submit_button.layout( + &mut color_picker.tree.children[1], + renderer, + &submit_limits, + ); cancel_button.move_to(Point::new( cancel_button.bounds().x + PADDING, cancel_button.bounds().y + rgba_colors.bounds().height - + hex_text.bounds().height + + hex_text_layout.bounds().height + PADDING + 2.0 * SPACING, )); @@ -1009,7 +1040,7 @@ where + PADDING, submit_button.bounds().y + rgba_colors.bounds().height - + hex_text.bounds().height + + hex_text_layout.bounds().height + PADDING + 2.0 * SPACING, )); @@ -1018,12 +1049,12 @@ where Size::new( rgba_colors.bounds().width + (2.0 * PADDING), rgba_colors.bounds().height - + hex_text.bounds().height + + hex_text_layout.bounds().height + cancel_button.bounds().height + (2.0 * PADDING) + (2.0 * SPACING), ), - vec![rgba_colors, hex_text, cancel_button, submit_button], + vec![rgba_colors, hex_text_layout, cancel_button, submit_button], ); block2_node.move_to(Point::new(bounds.x, bounds.y)); @@ -1376,21 +1407,24 @@ fn rgba_color( .expect("Graphics: Layout should have a value layout"); // Label - renderer.fill_text(Text { - content: label, - bounds: Rectangle { - x: label_layout.bounds().center_x(), - y: label_layout.bounds().center_y(), - ..label_layout.bounds() + renderer.fill_text( + Text { + content: label, + bounds: Size::new(label_layout.bounds().width, label_layout.bounds().height), + size: renderer.default_size(), + font: crate::ICON_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Advanced, }, - size: renderer.default_size(), - font: crate::BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Advanced, - color: style.text_color, - }); + Point::new( + label_layout.bounds().center_x(), + label_layout.bounds().center_y(), + ), + style.text_color, + label_layout.bounds(), + ); let bounds = bar_layout.bounds(); @@ -1445,21 +1479,24 @@ fn rgba_color( ); // Value - renderer.fill_text(Text { - content: &format!("{}", (255.0 * value) as u8), - bounds: Rectangle { - x: value_layout.bounds().center_x(), - y: value_layout.bounds().center_y(), - ..value_layout.bounds() + renderer.fill_text( + Text { + content: &format!("{}", (255.0 * value) as u8), + bounds: Size::new(value_layout.bounds().width, value_layout.bounds().height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: iced_widget::text::LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: renderer.default_size(), - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: iced_widget::text::LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - color: style.text_color, - }); + Point::new( + value_layout.bounds().center_x(), + value_layout.bounds().center_y(), + ), + style.text_color, + value_layout.bounds(), + ); if focus == target { renderer.fill_quad( @@ -1575,15 +1612,19 @@ fn hex_text( *color, ); - renderer.fill_text(Text { - content: &color.as_hex_string(), - bounds: Rectangle { - x: layout.bounds().center_x(), - y: layout.bounds().center_y(), - ..layout.bounds() + renderer.fill_text( + Text { + content: &color.as_hex_string(), + bounds: Size::new(layout.bounds().width, layout.bounds().height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: iced_widget::text::LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Basic, }, - size: renderer.default_size(), - color: Color { + Point::new(layout.bounds().center_x(), layout.bounds().center_y()), + Color { a: 1.0, ..Hsv { hue: 0, @@ -1592,12 +1633,8 @@ fn hex_text( } .into() }, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: iced_widget::text::LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Basic, - }); + layout.bounds(), + ); } /// The state of the [`ColorPickerOverlay`]. @@ -1699,7 +1736,7 @@ where unimplemented!("This should never be reached!") } - fn layout(&self, _renderer: &Renderer, _limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, _limits: &Limits) -> Node { unimplemented!("This should never be reached!") } diff --git a/src/native/overlay/context_menu.rs b/src/native/overlay/context_menu.rs index 34ee7911..dbfbe630 100644 --- a/src/native/overlay/context_menu.rs +++ b/src/native/overlay/context_menu.rs @@ -12,7 +12,7 @@ use iced_widget::core::{ mouse::{self, Cursor}, overlay, renderer, touch, widget::tree::Tree, - window, Clipboard, Color, Element, Event, Layout, Point, Rectangle, Shell, Size, + window, Clipboard, Color, Element, Event, Layout, Point, Rectangle, Shell, Size, Vector, }; /// The overlay of the [`ContextMenu`](crate::native::ContextMenu). @@ -70,11 +70,20 @@ where Renderer: 'a + core::Renderer, Renderer::Theme: StyleSheet, { - fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + _translation: Vector, + ) -> Node { let limits = Limits::new(Size::ZERO, bounds); let max_size = limits.max(); - let mut content = self.content.as_widget().layout(renderer, &limits); + let mut content = self + .content + .as_widget() + .layout(self.tree, renderer, &limits); // Try to stay inside borders let mut position = position; @@ -174,7 +183,7 @@ where Status::Captured } - Event::Window(window::Event::Resized { .. }) => { + Event::Window(_id, window::Event::Resized { .. }) => { self.state.show = false; forward_event_to_children = false; Status::Captured diff --git a/src/native/overlay/date_picker.rs b/src/native/overlay/date_picker.rs index 401e4017..9d87477d 100644 --- a/src/native/overlay/date_picker.rs +++ b/src/native/overlay/date_picker.rs @@ -32,7 +32,7 @@ use iced_widget::{ touch, widget::tree::Tree, Alignment, Clipboard, Color, Element, Event, Layout, Length, Overlay, Padding, Point, - Rectangle, Renderer as _, Shell, Size, Widget, + Rectangle, Renderer as _, Shell, Size, Vector, Widget, }, renderer::Renderer, text, Button, Column, Container, Row, Text, @@ -371,7 +371,13 @@ where Theme: 'a + StyleSheet + button::StyleSheet + text::StyleSheet + container::StyleSheet, { #[allow(clippy::too_many_lines)] - fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + _translation: Vector, + ) -> Node { let limits = Limits::new(Size::ZERO, bounds) .pad(Padding::from(PADDING)) .width(Length::Fill) @@ -381,42 +387,37 @@ where // Pre-Buttons TODO: get rid of it let cancel_limits = limits; - let cancel_button = self.cancel_button.layout(renderer, &cancel_limits); + let cancel_button = + self.cancel_button + .layout(&mut self.tree.children[0], renderer, &cancel_limits); let limits = limits.shrink(Size::new(0.0, cancel_button.bounds().height + SPACING)); // Month/Year let font_size = renderer.default_size(); - let month_year = Row::<(), Renderer>::new() + let month_year = Row::>::new() .width(Length::Fill) .spacing(SPACING) .push( Row::new() .width(Length::Fill) - .push( - Container::new( - Row::new() // Left Month arrow - .width(Length::Shrink) - .push( - Text::new(char::from(BootstrapIcon::CaretLeftFill).to_string()) - .size(font_size + 1.0) - .font(BOOTSTRAP_FONT), - ), - ) - .height(Length::Shrink), - ) + .push(Container::new( + Row::new().push( + Text::new(char::from(Icon::CaretLeftFill).to_string()) + .size(font_size.0 + 1.0) + .font(crate::BOOTSTRAP_FONT), + ), + )) .push( // Month - Text::new("October") - .width(Length::Fill) - .height(Length::Shrink), + Text::new("October").width(Length::Fill), ) .push( // Right Month arrow Container::new( - Text::new(char::from(BootstrapIcon::CaretRightFill).to_string()) - .size(font_size + 1.0) + Text::new(char::from(Icon::CaretRightFill).to_string()) + .size(font_size.0 + 1.0) .font(crate::BOOTSTRAP_FONT), ) .height(Length::Shrink) @@ -426,43 +427,32 @@ where .push( Row::new() .width(Length::Fill) - .push( - Container::new( - Row::new() // Left Year arrow - .width(Length::Shrink) - .push( - Text::new(char::from(BootstrapIcon::CaretLeftFill).to_string()) - .size(font_size + 1.0) - .font(BOOTSTRAP_FONT), - ), - ) - .height(Length::Shrink) - .width(Length::Shrink), - ) + + .push(Container::new( + Row::new().push( + Text::new(char::from(Icon::CaretLeftFill).to_string()) + .size(font_size.0 + 1.0) + .font(BOOTSTRAP_FONT), + ), + )) .push( // Year - Text::new("9999").width(Length::Fill).height(Length::Shrink), + Text::new("9999").width(Length::Fill), ) .push( // Right Year arrow Container::new( - Row::new() - .width(Length::Shrink) - .height(Length::Shrink) - .push( - Text::new( - char::from(BootstrapIcon::CaretRightFill).to_string(), - ) - .size(font_size + 1.0) + Row::new().push( + Text::new(char::from(Icon::CaretRightFill).to_string()) + .size(font_size.0 + 1.0) .font(BOOTSTRAP_FONT), - ), - ) - .height(Length::Shrink) + ), + ) .height(Length::Shrink) .width(Length::Shrink), ), ); - let days = Container::<(), Renderer>::new((0..7).fold( + let days = Container::>::new((0..7).fold( Column::new().width(Length::Fill).height(Length::Fill), |column, _y| { column.push( @@ -473,14 +463,9 @@ where .padding(DAY_CELL_PADDING), |row, _x| { row.push( - Container::new( - Row::new() - .width(Length::Shrink) - .height(Length::Shrink) - .push(Text::new("31").size(font_size)), - ) - .width(Length::Fill) - .height(Length::Shrink), + Container::new(Row::new().push(Text::new("31").size(font_size))) + .width(Length::Fill) + .height(Length::Fill), ) }, ), @@ -491,13 +476,23 @@ where .height(Length::Fill) .center_y(); - let mut col = Column::<(), Renderer>::new() + let col = Column::>::new() .spacing(SPACING) .align_items(Alignment::Center) .push(month_year) - .push(days) - .layout(renderer, &limits); + .push(days); + + let element: Element> = Element::new(col); + let col_tree = if let Some(child_tree) = self.tree.children.get_mut(2) { + child_tree.diff(element.as_widget()); + child_tree + } else { + let child_tree = Tree::new(element.as_widget()); + self.tree.children.insert(2, child_tree); + &mut self.tree.children[2] + }; + let mut col = element.as_widget().layout(col_tree, renderer, &limits); col.move_to(Point::new( col.bounds().x + PADDING, col.bounds().y + PADDING, @@ -507,12 +502,16 @@ where let cancel_limits = limits.max_width(((col.bounds().width / 2.0) - BUTTON_SPACING).max(0.0)); - let mut cancel_button = self.cancel_button.layout(renderer, &cancel_limits); + let mut cancel_button = + self.cancel_button + .layout(&mut self.tree.children[0], renderer, &cancel_limits); let submit_limits = limits.max_width(((col.bounds().width / 2.0) - BUTTON_SPACING).max(0.0)); - let mut submit_button = self.submit_button.layout(renderer, &submit_limits); + let mut submit_button = + self.submit_button + .layout(&mut self.tree.children[1], renderer, &submit_limits); cancel_button.move_to(Point { x: cancel_button.bounds().x + PADDING, @@ -965,7 +964,7 @@ where unimplemented!("This should never be reached!") } - fn layout(&self, _renderer: &Renderer, _limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, _limits: &Limits) -> Node { unimplemented!("This should never be reached!") } @@ -1129,64 +1128,69 @@ fn month_year( let mut buffer = [0; 4]; // Left caret - renderer.fill_text(core::text::Text { - content: char::from(BootstrapIcon::CaretLeftFill).encode_utf8(&mut buffer), - bounds: Rectangle { - x: left_bounds.center_x(), - y: left_bounds.center_y(), - ..left_bounds + + renderer.fill_text( + core::text::Text { + content: char::from(Icon::CaretLeftFill).encode_utf8(&mut buffer), + bounds: Size::new(left_bounds.width, left_bounds.height), + size: core::Pixels( + renderer.default_size().0 + if left_arrow_hovered { 1.0 } else { 0.0 }, + ), + font: BOOTSTRAP_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Advanced, }, - size: renderer.default_size() + if left_arrow_hovered { 1.0 } else { 0.0 }, - color: style + Point::new(left_bounds.center_x(), left_bounds.center_y()), + style .get(&style_state) .expect("Style Sheet not found.") .text_color, - font: BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Advanced, - }); + left_bounds, + ); // Text - renderer.fill_text(core::text::Text { - content: text, - bounds: Rectangle { - x: center_bounds.center_x(), - y: center_bounds.center_y(), - ..center_bounds + renderer.fill_text( + core::text::Text { + content: text, + bounds: Size::new(center_bounds.width, center_bounds.height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: style + Point::new(center_bounds.center_x(), center_bounds.center_y()), + style .get(&style_state) .expect("Style Sheet not found.") .text_color, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + center_bounds, + ); // Right caret - renderer.fill_text(core::text::Text { - content: char::from(BootstrapIcon::CaretRightFill).encode_utf8(&mut buffer), - bounds: Rectangle { - x: right_bounds.center_x(), - y: right_bounds.center_y(), - ..right_bounds + renderer.fill_text( + core::text::Text { + content: char::from(BootstrapIcon::CaretRightFill).encode_utf8(&mut buffer), + bounds: Size::new(right_bounds.width, right_bounds.height), + size: core::Pixels( + renderer.default_size().0 + if right_arrow_hovered { 1.0 } else { 0.0 }, + ), + font: BOOTSTRAP_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Advanced, }, - size: renderer.default_size() + if right_arrow_hovered { 1.0 } else { 0.0 }, - color: style + Point::new(right_bounds.center_x(), right_bounds.center_y()), + style .get(&style_state) .expect("Style Sheet not found.") .text_color, - font: BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Advanced, - }); + right_bounds, + ); }; // Draw month @@ -1230,24 +1234,24 @@ fn day_labels( for (i, label) in layout.children().enumerate() { let bounds = label.bounds(); - renderer.fill_text(core::text::Text { - content: &crate::core::date::WEEKDAY_LABELS[i], - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds + renderer.fill_text( + core::text::Text { + content: &crate::core::date::WEEKDAY_LABELS[i], + bounds: Size::new(bounds.width, bounds.height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: style + Point::new(bounds.center_x(), bounds.center_y()), + style .get(&StyleState::Active) .expect("Style Sheet not found.") .text_color, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + bounds, + ); } } @@ -1315,15 +1319,19 @@ fn day_table( ); } - renderer.fill_text(core::text::Text { - content: &format!("{number:02}"), // Todo: is there some way of static format as this has a fixed size? - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds + renderer.fill_text( + core::text::Text { + content: &format!("{number:02}"), // Todo: is there some way of static format as this has a fixed size? + bounds: Size::new(bounds.width, bounds.height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: if is_in_month == IsInMonth::Same { + Point::new(bounds.center_x(), bounds.center_y()), + if is_in_month == IsInMonth::Same { style .get(&style_state) .expect("Style Sheet not found.") @@ -1334,12 +1342,8 @@ fn day_table( .expect("Style Sheet not found.") .text_attenuated_color }, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + bounds, + ); } } } diff --git a/src/native/overlay/floating_element.rs b/src/native/overlay/floating_element.rs index da783e43..14a35869 100644 --- a/src/native/overlay/floating_element.rs +++ b/src/native/overlay/floating_element.rs @@ -9,7 +9,7 @@ use iced_widget::core::{ mouse::{self, Cursor}, overlay, renderer, widget::Tree, - Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, + Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, }; /// The internal overlay of a [`FloatingElement`](crate::FloatingElement) for @@ -56,12 +56,21 @@ impl<'a, 'b, Message, Renderer> core::Overlay where Renderer: core::Renderer, { - fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node { + fn layout( + &mut self, + renderer: &Renderer, + _bounds: Size, + position: Point, + _translation: Vector, + ) -> layout::Node { // Constrain overlay to fit inside the underlay's bounds let limits = layout::Limits::new(Size::ZERO, self.underlay_bounds.size()) .width(Length::Fill) .height(Length::Fill); - let mut node = self.element.as_widget().layout(renderer, &limits); + let mut node = self + .element + .as_widget() + .layout(self.state, renderer, &limits); let position = match self.anchor { Anchor::NorthWest => Point::new(position.x + self.offset.x, position.y + self.offset.y), diff --git a/src/native/overlay/modal.rs b/src/native/overlay/modal.rs index 0f5e3c3b..15d6d021 100644 --- a/src/native/overlay/modal.rs +++ b/src/native/overlay/modal.rs @@ -7,6 +7,7 @@ use iced_widget::core::{ renderer, touch, widget::Tree, Alignment, Clipboard, Color, Element, Event, Layout, Overlay, Point, Rectangle, Shell, Size, + Vector, }; use crate::style::modal::StyleSheet; @@ -68,9 +69,18 @@ where Renderer: core::Renderer, Renderer::Theme: StyleSheet, { - fn layout(&self, renderer: &Renderer, bounds: Size, _position: Point) -> layout::Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + _position: Point, + _translation: Vector, + ) -> layout::Node { let limits = layout::Limits::new(Size::ZERO, bounds); - let mut content = self.content.as_widget().layout(renderer, &limits); + let mut content = self + .content + .as_widget() + .layout(self.state, renderer, &limits); let max_size = limits.max(); content.align( diff --git a/src/native/overlay/time_picker.rs b/src/native/overlay/time_picker.rs index 2ce3b98d..3fa0179f 100644 --- a/src/native/overlay/time_picker.rs +++ b/src/native/overlay/time_picker.rs @@ -505,7 +505,13 @@ where Message: 'static + Clone, Theme: 'a + StyleSheet + button::StyleSheet + text::StyleSheet + container::StyleSheet, { - fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + _translation: Vector, + ) -> Node { let limits = Limits::new(Size::ZERO, bounds) .pad(Padding::from(PADDING)) .width(Length::Fill) @@ -519,7 +525,9 @@ where // Pre-Buttons TODO: get rid of it let cancel_limits = limits; - let cancel_button = self.cancel_button.layout(renderer, &cancel_limits); + let cancel_button = + self.cancel_button + .layout(&mut self.tree.children[0], renderer, &cancel_limits); let limits = limits.shrink(Size::new( 0.0, @@ -530,7 +538,7 @@ where let mut clock = Row::<(), Renderer>::new() .width(Length::Fill) .height(Length::Fill) - .layout(renderer, &limits); + .layout(self.tree, renderer, &limits); clock.move_to(Point::new( clock.bounds().x + PADDING, @@ -546,12 +554,16 @@ where let cancel_limits = limits.max_width(((clock.bounds().width / 2.0) - BUTTON_SPACING).max(0.0)); - let mut cancel_button = self.cancel_button.layout(renderer, &cancel_limits); + let mut cancel_button = + self.cancel_button + .layout(&mut self.tree.children[0], renderer, &cancel_limits); let submit_limits = limits.max_width(((clock.bounds().width / 2.0) - BUTTON_SPACING).max(0.0)); - let mut submit_button = self.submit_button.layout(renderer, &submit_limits); + let mut submit_button = + self.submit_button + .layout(&mut self.tree.children[1], renderer, &submit_limits); cancel_button.move_to(Point { x: cancel_button.bounds().x + PADDING, @@ -921,7 +933,7 @@ where /// Defines the layout of the digital clock of the time picker. fn digital_clock( - time_picker: &TimePickerOverlay<'_, Message, Theme>, + time_picker: &mut TimePickerOverlay<'_, Message, Theme>, renderer: &Renderer, limits: Limits, ) -> Node @@ -929,10 +941,10 @@ where Message: 'static + Clone, Theme: StyleSheet + button::StyleSheet + text::StyleSheet + container::StyleSheet, { - let arrow_size = renderer.default_size(); - let font_size = 1.2 * renderer.default_size(); + let arrow_size = renderer.default_size().0; + let font_size = 1.2 * renderer.default_size().0; - let mut digital_clock_row = Row::<(), Renderer>::new() + let mut digital_clock_row = Row::>::new() .align_items(Alignment::Center) .height(Length::Shrink) .width(Length::Shrink) @@ -1034,12 +1046,25 @@ where ); } - Container::new(digital_clock_row) + let container = Container::new(digital_clock_row) .width(Length::Fill) .height(Length::Shrink) .center_x() - .center_y() - .layout(renderer, &limits) + .center_y(); + + let element: Element> = Element::new(container); + let container_tree = if let Some(child_tree) = time_picker.tree.children.get_mut(2) { + child_tree.diff(element.as_widget()); + child_tree + } else { + let child_tree = Tree::new(element.as_widget()); + time_picker.tree.children.insert(2, child_tree); + &mut time_picker.tree.children[2] + }; + + element + .as_widget() + .layout(container_tree, renderer, &limits) } /// Draws the analog clock. @@ -1191,7 +1216,7 @@ fn draw_clock( .get(&clock_style_state) .expect("Style Sheet not found.") .clock_number_color, - size: period_size, + size: core::Pixels(period_size), font: renderer.default_font(), horizontal_alignment: Horizontal::Center, vertical_alignment: Vertical::Center, @@ -1236,7 +1261,7 @@ fn draw_clock( .get(&style_state) .expect("Style Sheet not found.") .clock_number_color, - size: number_size, + size: core::Pixels(number_size), font: renderer.default_font(), horizontal_alignment: Horizontal::Center, vertical_alignment: Vertical::Center, @@ -1271,7 +1296,7 @@ fn draw_clock( .get(&style_state) .expect("Style Sheet not found.") .clock_number_color, - size: number_size, + size: core::Pixels(number_size), font: renderer.default_font(), horizontal_alignment: Horizontal::Center, vertical_alignment: Vertical::Center, @@ -1317,7 +1342,7 @@ fn draw_clock( .get(&style_state) .expect("Style Sheet not found.") .clock_number_color, - size: number_size, + size: core::Pixels(number_size), font: renderer.default_font(), horizontal_alignment: Horizontal::Center, vertical_alignment: Vertical::Center, @@ -1419,64 +1444,62 @@ fn draw_digital_clock( let mut buffer = [0; 4]; // Caret up - renderer.fill_text(core::Text { - content: char::from(BootstrapIcon::CaretUpFill).encode_utf8(&mut buffer), - bounds: Rectangle { - x: up_bounds.center_x(), - y: up_bounds.center_y(), - ..up_bounds + renderer.fill_text( + core::Text { + content: char::from(BootstrapIcon::CaretUpFill).encode_utf8(&mut buffer), + bounds: Size::new(up_bounds.width, up_bounds.height), + size: core::Pixels( + renderer.default_size().0 + if up_arrow_hovered { 1.0 } else { 0.0 }, + ), + font: crate::graphics::icons::BOOTSTRAP_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size() + if up_arrow_hovered { 1.0 } else { 0.0 }, - color: style + Point::new(up_bounds.center_x(), up_bounds.center_y()), + style .get(&StyleState::Active) .expect("Style Sheet not found.") .text_color, - font: crate::graphics::icons::BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + up_bounds, + ); // Text - renderer.fill_text(core::Text { - content: &text, - bounds: Rectangle { - x: center_bounds.center_x(), - y: center_bounds.center_y(), - ..center_bounds + renderer.fill_text( + core::Text { + content: &text, + bounds: Size::new(center_bounds.width, center_bounds.height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: style + Point::new(center_bounds.center_x(), center_bounds.center_y()), + style .get(&StyleState::Active) .expect("Style Sheet not found.") .text_color, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + center_bounds, + ); // Down caret - renderer.fill_text(core::Text { - content: char::from(BootstrapIcon::CaretDownFill).encode_utf8(&mut buffer), - bounds: Rectangle { - x: down_bounds.center_x(), - y: down_bounds.center_y(), - ..down_bounds - }, - size: renderer.default_size() + if down_arrow_hovered { 1.0 } else { 0.0 }, - color: style - .get(&StyleState::Active) - .expect("Style Sheet not found.") - .text_color, - font: crate::graphics::icons::BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + renderer.fill_text( + core::Text { + content: char::from(BootstrapIcon::CaretDownFill).encode_utf8(&mut buffer), + bounds: Size::new(down_bounds.width, down_bounds.height), + size: core::Pixels( + renderer.default_size().0 + if down_arrow_hovered { 1.0 } else { 0.0 }, + ), + font: crate::graphics::icons::BOOTSTRAP_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, + down_bounds, + ); }; if !time_picker.state.use_24h { @@ -1506,21 +1529,28 @@ fn draw_digital_clock( let hour_minute_separator = children .next() .expect("Graphics: Layout should have a hour/minute separator layout"); - renderer.fill_text(core::Text { - content: ":", - bounds: Rectangle { - x: hour_minute_separator.bounds().center_x(), - y: hour_minute_separator.bounds().center_y(), - ..hour_minute_separator.bounds() + + renderer.fill_text( + core::Text { + content: ":", + bounds: Size::new( + hour_minute_separator.bounds().width, + hour_minute_separator.bounds().height, + ), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: style[&StyleState::Active].text_color, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + Point::new( + hour_minute_separator.bounds().center_x(), + hour_minute_separator.bounds().center_y(), + ), + style[&StyleState::Active].text_color, + hour_minute_separator.bounds(), + ); // Draw minutes let minute_layout = children @@ -1538,24 +1568,27 @@ fn draw_digital_clock( let minute_second_separator = children .next() .expect("Graphics: Layout should have a minute/second separator layout"); - renderer.fill_text(core::Text { - content: ":", - bounds: Rectangle { - x: minute_second_separator.bounds().center_x(), - y: minute_second_separator.bounds().center_y(), - ..minute_second_separator.bounds() + renderer.fill_text( + core::Text { + content: ":", + bounds: Size::new( + minute_second_separator.bounds().width, + minute_second_separator.bounds().height, + ), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: style - .get(&StyleState::Active) - .expect("Style Sheet not found.") - .text_color, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + Point::new( + minute_second_separator.bounds().center_x(), + minute_second_separator.bounds().center_y(), + ), + style[&StyleState::Active].text_color, + minute_second_separator.bounds(), + ); // Draw seconds let second_layout = children @@ -1574,28 +1607,25 @@ fn draw_digital_clock( let period = children .next() .expect("Graphics: Layout should have a period layout"); - renderer.fill_text(core::Text { - content: if time_picker.state.time.hour12().0 { - "PM" - } else { - "AM" - }, - bounds: Rectangle { - x: period.bounds().center_x(), - y: period.bounds().center_y(), - ..period.bounds() + renderer.fill_text( + core::Text { + content: if time_picker.state.time.hour12().0 { + "PM" + } else { + "AM" + }, + bounds: Size::new(period.bounds().width, period.bounds().height), + size: renderer.default_size(), + font: renderer.default_font(), + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: text::LineHeight::Relative(1.3), + shaping: text::Shaping::Basic, }, - size: renderer.default_size(), - color: style - .get(&StyleState::Active) - .expect("Style Sheet not found.") - .text_color, - font: renderer.default_font(), - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: text::LineHeight::Relative(1.3), - shaping: text::Shaping::Basic, - }); + Point::new(period.bounds().center_x(), period.bounds().center_y()), + style[&StyleState::Active].text_color, + period.bounds(), + ); } } @@ -1710,7 +1740,7 @@ where unimplemented!("This should never be reached!") } - fn layout(&self, _renderer: &Renderer, _limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, _limits: &Limits) -> Node { unimplemented!("This should never be reached!") } diff --git a/src/native/quad.rs b/src/native/quad.rs index 6946eb34..a09eb0e1 100644 --- a/src/native/quad.rs +++ b/src/native/quad.rs @@ -7,66 +7,10 @@ use iced_widget::core::{ mouse::Cursor, renderer, widget::Tree, - Color, Element, Layout, Length, Padding, Rectangle, Widget, + Color, Element, Layout, Length, Rectangle, Widget, }; -/// Methods for creating inner bounds -#[allow(missing_debug_implementations)] -pub enum InnerBounds { - /// Create inner bounds ratio to the outer bounds - Ratio(f32, f32), - /// Create inner bounds by padding the outer bounds - Padding(Padding), - /// Create square inner bounds - Square(f32), - /// Create inner bounds with a custom function - Custom(Box Rectangle>), -} -impl InnerBounds { - /// Gets the inner bounds of the Set type. - fn get_bounds(&self, outer_bounds: Rectangle) -> Rectangle { - use InnerBounds::{Custom, Padding, Ratio, Square}; - match self { - Ratio(w, h) => { - let width = w * outer_bounds.width; - let height = h * outer_bounds.height; - let x = outer_bounds.x + (outer_bounds.width - width) * 0.5; - let y = outer_bounds.y + (outer_bounds.height - height) * 0.5; - Rectangle { - x, - y, - width, - height, - } - } - Padding(p) => { - let x = outer_bounds.x + p.left; - let y = outer_bounds.y + p.top; - let width = outer_bounds.width - p.horizontal(); - let height = outer_bounds.width - p.vertical(); - Rectangle { - x, - y, - width, - height, - } - } - Square(l) => { - let width = *l; - let height = *l; - let x = outer_bounds.x + (outer_bounds.width - width) * 0.5; - let y = outer_bounds.y + (outer_bounds.height - height) * 0.5; - Rectangle { - x, - y, - width, - height, - } - } - Custom(f) => f(outer_bounds), - } - } -} +use crate::native::InnerBounds; /// A dummy widget that draws a quad #[allow(missing_debug_implementations)] @@ -115,7 +59,7 @@ where self.height } - fn layout(&self, _renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { let limits = limits.width(self.width).height(self.height); Node::new(limits.max()) } diff --git a/src/native/segmented_button.rs b/src/native/segmented_button.rs index ef02d26e..e716a47c 100644 --- a/src/native/segmented_button.rs +++ b/src/native/segmented_button.rs @@ -141,7 +141,7 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let padding = self.padding; let limits = limits .loose() @@ -149,7 +149,10 @@ where .height(self.height) .pad(padding); - let mut content = self.content.as_widget().layout(renderer, &limits.loose()); + let mut content = + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, &limits.loose()); let size = limits.resolve(content.size()); content.move_to(Point::new(padding.left, padding.top)); diff --git a/src/native/selection_list.rs b/src/native/selection_list.rs index cdbfcc38..82162ab1 100644 --- a/src/native/selection_list.rs +++ b/src/native/selection_list.rs @@ -5,13 +5,17 @@ use crate::style::selection_list::StyleSheet; use iced_widget::{ container, core::{ - self, event, + self, + alignment::{Horizontal, Vertical}, + event, layout::{Limits, Node}, mouse::{self, Cursor}, renderer, - widget::Tree, - Clipboard, Element, Event, Layout, Length, Rectangle, Shell, Size, Widget, + text::{Paragraph, Text}, + widget::{tree, Tree}, + Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, }, + graphics, runtime::Font, scrollable, text, text::LineHeight, @@ -147,7 +151,7 @@ where impl<'a, T, Message, Renderer> Widget for SelectionList<'a, T, Message, Renderer> where - T: 'a + Clone + ToString + Eq + Hash, + T: 'a + Clone + ToString + Eq + Hash + Display, Message: 'static, Renderer: core::Renderer + core::text::Renderer + 'a, Renderer::Theme: StyleSheet + container::StyleSheet, @@ -158,6 +162,13 @@ where fn diff(&self, tree: &mut Tree) { tree.diff_children(&[&self.container as &dyn Widget<_, _>]); + let state = tree.state.downcast_mut::(); + + state.values = self + .options + .iter() + .map(|_| graphics::text::Paragraph::new()) + .collect() } fn width(&self) -> Length { @@ -168,37 +179,51 @@ where Length::Shrink } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new(self.options)) + } + + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { use std::f32; + let state = tree.state.downcast_mut::(); + let limits = limits.width(self.width).height(self.height); let max_width = match self.width { - Length::Shrink => { - let labels = self.options.iter().map(ToString::to_string); + Length::Shrink => self + .options + .iter() + .enumerate() + .map(|(id, val)| { + let text = Text { + content: &val.to_string(), + size: Pixels(self.text_size), + line_height: LineHeight::default(), + bounds: Size::INFINITY, + font: self.font, + horizontal_alignment: Horizontal::Left, + vertical_alignment: Vertical::Top, + shaping: text::Shaping::Advanced, + }; - labels - .map(|label| { - let size = renderer.measure( - &label, - self.text_size, - LineHeight::default(), - self.font, - Size::new(f32::INFINITY, f32::INFINITY), - text::Shaping::Advanced, - ); - - size.width.round() as u32 + self.padding as u32 * 2 - }) - .max() - .unwrap_or(100) - } + state.values[id].update(text); + state.values[id].min_bounds().width.round() as u32 + self.padding as u32 * 2 + }) + .max() + .unwrap_or(100), _ => limits.max().width as u32, }; let limits = limits.max_width(max_width as f32 + self.padding * 2.0); - let content = self.container.layout(renderer, &limits); + let content = self + .container + .layout(&mut tree.children[0], renderer, &limits); let size = limits.resolve(content.size()); Node::with_children(size, vec![content]) } @@ -279,7 +304,7 @@ where impl<'a, T, Message, Renderer> From> for Element<'a, Message, Renderer> where - T: Clone + ToString + Eq + Hash, + T: Clone + ToString + Eq + Hash + Display, Message: 'static, Renderer: 'a + core::Renderer + core::text::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, @@ -288,3 +313,25 @@ where Element::new(selection_list) } } + +/// A Paragraph cache to enhance speed of layouting. +#[derive(Debug, Default, Clone)] +pub struct State { + values: Vec, +} + +impl State { + /// Creates a new [`State`], representing an unfocused [`TextInput`]. + pub fn new<'a, T>(options: &'a [T]) -> Self + where + T: Clone + Display + Eq + Hash, + [T]: ToOwned>, + { + Self { + values: options + .iter() + .map(|_| graphics::text::Paragraph::new()) + .collect(), + } + } +} diff --git a/src/native/selection_list/list.rs b/src/native/selection_list/list.rs index 2de9a5bc..fd70c671 100644 --- a/src/native/selection_list/list.rs +++ b/src/native/selection_list/list.rs @@ -13,7 +13,8 @@ use iced_widget::{ tree::{State, Tag}, Tree, }, - Clipboard, Color, Element, Event, Layout, Length, Rectangle, Shell, Size, Widget, + Clipboard, Color, Element, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, + Widget, }, text::LineHeight, }; @@ -59,6 +60,8 @@ pub struct ListState { pub hovered_option: Option, /// The index in the list of options of the last chosen Item Clicked for Processing pub last_selected_index: Option<(usize, u64)>, + /// String Build Cache + pub options: Vec, } impl<'a, T, Message, Renderer> Widget for List<'a, T, Message, Renderer> @@ -72,7 +75,10 @@ where } fn state(&self) -> State { - State::new(ListState::default()) + State::new(ListState { + options: self.options.iter().map(ToString::to_string).collect(), + ..ListState::default() + }) } fn diff(&self, state: &mut Tree) { @@ -99,6 +105,8 @@ where list_state.last_selected_index = None; } } + + list_state.options = self.options.iter().map(ToString::to_string).collect(); } fn width(&self) -> Length { @@ -109,7 +117,12 @@ where Length::Shrink } - fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + _tree: &mut Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { use std::f32; let limits = limits.height(Length::Fill).width(Length::Fill); @@ -212,12 +225,9 @@ where let offset = viewport.y - bounds.y; let start = (offset / option_height) as usize; let end = ((offset + viewport.height) / option_height).ceil() as usize; - - let visible_options = &self.options[start..end.min(self.options.len())]; let list_state = state.state.downcast_ref::(); - for (i, option) in visible_options.iter().enumerate() { - let i = start + i; + for i in start..end.min(self.options.len()) { let is_selected = list_state .last_selected_index .map(|u| u.0 == i) @@ -255,22 +265,21 @@ where theme.style(&self.style).text_color }; - renderer.fill_text(core::text::Text { - content: &option.to_string(), - bounds: Rectangle { - x: bounds.x, - y: bounds.center_y(), - width: f32::INFINITY, - ..bounds + renderer.fill_text( + core::text::Text { + content: &list_state.options[i], + bounds: Size::new(f32::INFINITY, bounds.height), + size: Pixels(self.text_size), + font: self.font, + horizontal_alignment: Horizontal::Left, + vertical_alignment: Vertical::Center, + line_height: LineHeight::default(), + shaping: iced_widget::text::Shaping::Advanced, }, - size: self.text_size, - color: text_color, - font: self.font, - horizontal_alignment: Horizontal::Left, - vertical_alignment: Vertical::Center, - line_height: LineHeight::default(), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(bounds.x, bounds.center_y()), + text_color, + bounds, + ); } } } diff --git a/src/native/slide_bar.rs b/src/native/slide_bar.rs new file mode 100644 index 00000000..e420e774 --- /dev/null +++ b/src/native/slide_bar.rs @@ -0,0 +1,357 @@ +//! A dummy widget that draws a quad +//! +//! *This API requires the following crate features to be activated: `quad`* + +use iced_widget::core::{ + event, + layout::{Limits, Node}, + mouse::{self, Cursor}, + renderer, touch, + widget::{tree, Tree}, + Clipboard, Color, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, +}; + +use std::ops::RangeInclusive; + +/// Constant Default height of SliderBar. +pub const DEFAULT_HEIGHT: f32 = 30.0; + +/// A widget that draws a SlideBar +#[allow(missing_debug_implementations)] +pub struct SlideBar<'a, T, Message> +where + Message: Clone, +{ + /// Width of the bar + pub width: Length, + /// Height of the bar + pub height: Option, + /// Color of the bar + pub color: Color, + /// Background color of the bar + pub background: Option, + /// Border radius of the bar + pub border_radius: [f32; 4], + /// Border width of the bar + pub border_width: f32, + /// Border color of the bar + pub border_color: Color, + /// value Range + pub range: RangeInclusive, + /// smallest value within moveable limitations. + step: T, + /// Value of the bar + value: T, + /// Change event of the bar when a value is modified + on_change: Box Message + 'a>, + /// Release event when the mouse is released. + on_release: Option, +} + +impl<'a, T, Message> SlideBar<'a, T, Message> +where + T: Copy + From + std::cmp::PartialOrd, + Message: Clone, +{ + /// Creates a new [`SliderBar`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`SliderBar`] + /// * a function that will be called when the [`SliderBar`] is dragged. + /// It receives the new value of the [`SliderBar`] and must produce a + /// `Message`. + pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self + where + F: 'a + Fn(T) -> Message, + { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + + Self { + width: Length::Fill, + height: None, + color: Color::from([0.5; 3]), + background: None, + border_radius: [5.0; 4], + border_width: 1.0, + border_color: Color::BLACK, + step: T::from(1), + value, + range, + on_change: Box::new(on_change), + on_release: None, + } + } + + /// Sets the release message of the [`Slider`]. + /// This is called when the mouse is released from the slider. + /// + /// Typically, the user's interaction with the slider is finished when this message is produced. + /// This is useful if you need to spawn a long-running task from the slider's result, where + /// the default on_change message could create too many events. + pub fn on_release(mut self, on_release: Message) -> Self { + self.on_release = Some(on_release); + self + } + + /// Sets the width of the [`Slider`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Slider`]. + pub fn height(mut self, height: Option) -> Self { + self.height = height; + self + } + + /// Sets the step size of the [`Slider`]. + pub fn step(mut self, step: impl Into) -> Self { + self.step = step.into(); + self + } +} + +impl<'a, T, Message, Renderer> Widget for SlideBar<'a, T, Message> +where + T: Copy + Into + num_traits::FromPrimitive, + Message: Clone, + Renderer: renderer::Renderer, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height.unwrap_or(Length::Fixed(DEFAULT_HEIGHT)) + } + + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { + let limits = limits + .width(self.width) + .height(self.height.unwrap_or(Length::Fixed(DEFAULT_HEIGHT))); + + let size = limits.resolve(Size::ZERO); + + Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + update( + event, + layout, + cursor, + shell, + tree.state.downcast_mut::(), + &mut self.value, + &self.range, + self.step, + self.on_change.as_ref(), + &self.on_release, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + _theme: &::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor: Cursor, + _viewport: &Rectangle, + ) { + draw(renderer, layout, &self); + } +} + +/// Processes an [`Event`] and updates the [`State`] of a [`SliderBar`] +/// accordingly. +pub fn update( + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + state: &mut State, + value: &mut T, + range: &RangeInclusive, + step: T, + on_change: &dyn Fn(T) -> Message, + on_release: &Option, +) -> event::Status +where + T: Copy + Into + num_traits::FromPrimitive, + Message: Clone, +{ + let is_dragging = state.is_dragging; + + let mut change = |cursor_position: Point| { + let bounds = layout.bounds(); + let new_value = if cursor_position.x <= bounds.x { + *range.start() + } else if cursor_position.x >= bounds.x + bounds.width { + *range.end() + } else { + let step = step.into(); + let start = (*range.start()).into(); + let end = (*range.end()).into(); + + let percent = f64::from(cursor_position.x - bounds.x) / f64::from(bounds.width); + + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; + + if let Some(value) = T::from_f64(value) { + value + } else { + return; + } + }; + + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); + + *value = new_value; + } + }; + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor_position) = cursor.position_over(layout.bounds()) { + change(cursor_position); + state.is_dragging = true; + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if is_dragging { + if let Some(on_release) = on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if is_dragging { + let _ = cursor.position().map(change); + + return event::Status::Captured; + } + } + _ => {} + } + + event::Status::Ignored +} + +/// Draws a [`SliderBar`]. +pub fn draw(renderer: &mut R, layout: Layout<'_>, slider: &SlideBar) +where + T: Into + Copy, + Message: Clone, + R: renderer::Renderer, +{ + let bounds = layout.bounds(); + let value = slider.value.into() as f32; + let (range_start, range_end) = { + let (start, end) = slider.range.clone().into_inner(); + + (start.into() as f32, end.into() as f32) + }; + + let active_progress_bounds = if range_start >= range_end { + Rectangle { + width: 0.0, + ..bounds + } + } else { + Rectangle { + width: bounds.width * (value - range_start) / (range_end - range_start), + ..bounds + } + }; + + let background = slider.background.unwrap_or(Color::from([1.0; 3])); + + renderer.fill_quad( + renderer::Quad { + bounds: layout.bounds(), + border_radius: slider.border_radius.into(), + border_width: slider.border_width, + border_color: slider.border_color, + }, + background, + ); + + if active_progress_bounds.width > 0.0 { + renderer.fill_quad( + renderer::Quad { + bounds: active_progress_bounds, + border_radius: slider.border_radius.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + slider.color, + ); + } +} + +impl<'a, T, Message, Renderer> From> for Element<'a, Message, Renderer> +where + T: 'a + Copy + Into + num_traits::FromPrimitive, + Renderer: 'a + renderer::Renderer, + Message: 'a + Clone, +{ + fn from(value: SlideBar<'a, T, Message>) -> Self { + Self::new(value) + } +} + +/// The local state of a [`SliderBar`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State { + is_dragging: bool, +} + +impl State { + /// Creates a new [`State`]. + pub fn new() -> State { + State::default() + } +} diff --git a/src/native/spinner.rs b/src/native/spinner.rs index 4e425da9..18cbccb8 100644 --- a/src/native/spinner.rs +++ b/src/native/spinner.rs @@ -127,7 +127,7 @@ where self.height } - fn layout(&self, _renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { Node::new( limits .width(self.width) @@ -195,7 +195,7 @@ where let bounds = layout.bounds(); - if let Event::Window(window::Event::RedrawRequested(now)) = event { + if let Event::Window(_id, window::Event::RedrawRequested(now)) = event { if is_visible(&bounds) { let state = state.state.downcast_mut::(); let duration = (now - state.last_update).as_secs_f32(); diff --git a/src/native/split.rs b/src/native/split.rs index 7b3a76af..31f82aac 100644 --- a/src/native/split.rs +++ b/src/native/split.rs @@ -200,15 +200,15 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let space = Row::::new() .width(Length::Fill) .height(Length::Fill) - .layout(renderer, limits); + .layout(tree, renderer, limits); match self.axis { - Axis::Horizontal => horizontal_split(self, renderer, limits, &space), - Axis::Vertical => vertical_split(self, renderer, limits, &space), + Axis::Horizontal => horizontal_split(tree, self, renderer, limits, &space), + Axis::Vertical => vertical_split(tree, self, renderer, limits, &space), } } @@ -515,6 +515,7 @@ where /// Do a horizontal split. fn horizontal_split<'a, Message, Renderer>( + tree: &mut Tree, split: &Split<'a, Message, Renderer>, renderer: &Renderer, limits: &Limits, @@ -531,11 +532,13 @@ where space.bounds().size(), vec![ split.first.as_widget().layout( + &mut tree.children[0], renderer, &limits.clone().shrink(Size::new(0.0, space.bounds().height)), ), Node::new(Size::new(space.bounds().height, split.spacing)), split.second.as_widget().layout( + &mut tree.children[1], renderer, &limits.clone().shrink(Size::new(0.0, space.bounds().width)), ), @@ -560,7 +563,10 @@ where space.bounds().height - f32::from(divider_position), )) .pad(padding); - let mut first = split.first.as_widget().layout(renderer, &first_limits); + let mut first = split + .first + .as_widget() + .layout(&mut tree.children[0], renderer, &first_limits); first.move_to(Point::new( space.bounds().x + split.padding, space.bounds().y + split.padding, @@ -573,7 +579,11 @@ where .clone() .shrink(Size::new(0.0, f32::from(divider_position) + split.spacing)) .pad(padding); - let mut second = split.second.as_widget().layout(renderer, &second_limits); + let mut second = + split + .second + .as_widget() + .layout(&mut tree.children[1], renderer, &second_limits); second.move_to(Point::new( space.bounds().x + split.padding, space.bounds().y + f32::from(divider_position) + split.spacing + split.padding, @@ -584,6 +594,7 @@ where /// Do a vertical split. fn vertical_split<'a, Message, Renderer>( + tree: &mut Tree, split: &Split<'a, Message, Renderer>, renderer: &Renderer, limits: &Limits, @@ -600,11 +611,13 @@ where space.bounds().size(), vec![ split.first.as_widget().layout( + &mut tree.children[0], renderer, &limits.clone().shrink(Size::new(space.bounds().width, 0.0)), ), Node::new(Size::new(split.spacing, space.bounds().height)), split.second.as_widget().layout( + &mut tree.children[1], renderer, &limits.clone().shrink(Size::new(space.bounds().width, 0.0)), ), @@ -629,7 +642,10 @@ where 0.0, )) .pad(padding); - let mut first = split.first.as_widget().layout(renderer, &first_limits); + let mut first = split + .first + .as_widget() + .layout(&mut tree.children[0], renderer, &first_limits); first.move_to(Point::new( space.bounds().x + split.padding, space.bounds().y + split.padding, @@ -642,7 +658,11 @@ where .clone() .shrink(Size::new(f32::from(divider_position) + split.spacing, 0.0)) .pad(padding); - let mut second = split.second.as_widget().layout(renderer, &second_limits); + let mut second = + split + .second + .as_widget() + .layout(&mut tree.children[1], renderer, &second_limits); second.move_to(Point::new( space.bounds().x + f32::from(divider_position) + split.spacing + split.padding, space.bounds().y + split.padding, diff --git a/src/native/tab_bar.rs b/src/native/tab_bar.rs index 0ea0c5e8..c1507fcb 100644 --- a/src/native/tab_bar.rs +++ b/src/native/tab_bar.rs @@ -19,7 +19,8 @@ use iced_widget::{ mouse::{self, Cursor}, renderer, touch, widget::Tree, - Alignment, Clipboard, Color, Element, Event, Layout, Length, Rectangle, Shell, Widget, + Alignment, Clipboard, Color, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, + Widget, }, runtime::Font, text::{self, LineHeight}, @@ -31,7 +32,7 @@ pub use crate::style::tab_bar::{Appearance, StyleSheet}; pub use tab_label::TabLabel; /// The default icon size. -const DEFAULT_ICON_SIZE: f32 = 32.0; +const DEFAULT_ICON_SIZE: f32 = 16.0; /// The default text size. const DEFAULT_TEXT_SIZE: f32 = 16.0; /// The default size of the close icon. @@ -353,7 +354,12 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { fn layout_icon(icon: &char, size: f32, font: Option) -> Text<'_, Renderer> where Renderer: iced_widget::core::text::Renderer, @@ -364,7 +370,8 @@ where .size(size) .font(font.unwrap_or_default()) .horizontal_alignment(alignment::Horizontal::Center) - .vertical_alignment(alignment::Vertical::Center) + .vertical_alignment(alignment::Vertical::Bottom) + .shaping(text::Shaping::Advanced) } fn layout_text(text: &str, size: f32, font: Option) -> Text<'_, Renderer> @@ -377,10 +384,12 @@ where .size(size) .font(font.unwrap_or_default()) .horizontal_alignment(alignment::Horizontal::Center) - .vertical_alignment(alignment::Vertical::Center) + .vertical_alignment(alignment::Vertical::Bottom) + .shaping(text::Shaping::Advanced) } - self.tab_labels + let row = self + .tab_labels .iter() .fold(Row::::new(), |row, tab_label| { let mut label_row = Row::new() @@ -388,11 +397,12 @@ where match tab_label { TabLabel::Icon(icon) => Column::new() .align_items(Alignment::Center) - .push(layout_icon(icon, self.icon_size, self.font)), + .push(layout_icon(icon, self.icon_size + 1.0, self.icon_font)), TabLabel::Text(text) => Column::new() + .padding(5.0) .align_items(Alignment::Center) - .push(layout_text(text, self.icon_size, self.font)), + .push(layout_text(text, self.text_size + 1.0, self.text_font)), TabLabel::IconText(icon, text) => { let mut column = Column::new().align_items(Alignment::Center); @@ -400,29 +410,64 @@ where match self.position { Position::Top => { column = column - .push(layout_icon(icon, self.icon_size, self.font)) - .push(layout_text(text, self.icon_size, self.font)); + + .push(layout_icon( + icon, + self.icon_size + 1.0, + self.icon_font, + )) + .push(layout_text( + text, + self.text_size + 1.0, + self.text_font, + )); + } Position::Right => { column = column.push( Row::new() .align_items(Alignment::Center) - .push(layout_icon(icon, self.icon_size, self.font)) - .push(layout_text(text, self.icon_size, self.font)), + .push(layout_icon( + icon, + self.icon_size + 1.0, + self.icon_font, + )) + .push(layout_text( + text, + self.text_size + 1.0, + self.text_font, + )), + ); } Position::Left => { column = column.push( Row::new() .align_items(Alignment::Center) - .push(layout_text(text, self.icon_size, self.font)) - .push(layout_icon(icon, self.icon_size, self.font)), + .push(layout_text( + text, + self.icon_size + 1.0, + self.icon_font, + )) + .push(layout_icon( + icon, + self.text_size + 1.0, + self.text_font, + )), ); } Position::Bottom => { column = column - .push(layout_text(text, self.icon_size, self.font)) - .push(layout_icon(icon, self.icon_size, self.font)); + .push(layout_text( + text, + self.icon_size + 1.0, + self.icon_font, + )) + .push(layout_icon( + icon, + self.text_size + 1.0, + self.text_font, + )); } } @@ -450,7 +495,21 @@ where .width(self.width) .height(self.height) .spacing(self.spacing) - .layout(renderer, &limits.loose()) + .align_items(Alignment::Center); + + let element: Element = Element::new(row); + let tab_tree = if let Some(child_tree) = tree.children.get_mut(0) { + child_tree.diff(element.as_widget()); + child_tree + } else { + let child_tree = Tree::new(element.as_widget()); + tree.children.insert(0, child_tree); + &mut tree.children[0] + }; + + element + .as_widget() + .layout(tab_tree, renderer, &limits.loose()) } fn on_event( @@ -643,40 +702,41 @@ fn draw_tab( TabLabel::Icon(icon) => { let icon_bounds = icon_bound_rectangle(label_layout_children.next()); - renderer.fill_text(core::text::Text { - content: &icon.to_string(), - bounds: Rectangle { - x: icon_bounds.center_x(), - y: icon_bounds.center_y(), - ..icon_bounds + renderer.fill_text( + core::text::Text { + content: &icon.to_string(), + bounds: Size::new(icon_bounds.width, icon_bounds.height), + size: core::Pixels(icon_data.1), + font: icon_data.0, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: icon_data.1, - color: style.icon_color, - font: icon_data.0, - horizontal_alignment: Horizontal::Left, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(icon_bounds.center_x(), icon_bounds.center_y()), + style.icon_color, + icon_bounds, + ); } + TabLabel::Text(text) => { let text_bounds = text_bound_rectangle(label_layout_children.next()); - renderer.fill_text(core::text::Text { - content: &text[..], - bounds: Rectangle { - x: text_bounds.center_x(), - y: text_bounds.center_y(), - ..text_bounds + renderer.fill_text( + core::text::Text { + content: &text[..], + bounds: Size::new(text_bounds.width, text_bounds.height), + size: core::Pixels(text_data.1), + font: text_data.0, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: text_data.1, - color: style.text_color, - font: text_data.0, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(text_bounds.center_x(), text_bounds.center_y()), + style.text_color, + text_bounds, + ); } TabLabel::IconText(icon, text) => { let icon_bounds: Rectangle; @@ -709,37 +769,37 @@ fn draw_tab( } } - renderer.fill_text(core::text::Text { - content: &icon.to_string(), - bounds: Rectangle { - x: icon_bounds.center_x(), - y: icon_bounds.center_y(), - ..icon_bounds + renderer.fill_text( + core::text::Text { + content: &icon.to_string(), + bounds: Size::new(icon_bounds.width, icon_bounds.height), + size: core::Pixels(icon_data.1), + font: icon_data.0, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: icon_data.1, - color: style.icon_color, - font: icon_data.0, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); - - renderer.fill_text(core::text::Text { - content: &text[..], - bounds: Rectangle { - x: text_bounds.center_x(), - y: text_bounds.center_y(), - ..text_bounds + Point::new(icon_bounds.center_x(), icon_bounds.center_y()), + style.icon_color, + icon_bounds, + ); + + renderer.fill_text( + core::text::Text { + content: &text[..], + bounds: Size::new(text_bounds.width, text_bounds.height), + size: core::Pixels(text_data.1), + font: text_data.0, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Advanced, }, - size: text_data.1, - color: style.text_color, - font: text_data.0, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Advanced, - }); + Point::new(text_bounds.center_x(), text_bounds.center_y()), + style.text_color, + text_bounds, + ); } }; @@ -747,21 +807,22 @@ fn draw_tab( let cross_bounds = cross_layout.bounds(); let is_mouse_over_cross = cursor.is_over(cross_bounds); - renderer.fill_text(core::text::Text { - content: &icon_to_string(BootstrapIcon::X), - bounds: Rectangle { - x: cross_bounds.center_x(), - y: cross_bounds.center_y(), - ..cross_bounds + + renderer.fill_text( + core::text::Text { + content:&icon_to_string(BootstrapIcon::X), + bounds: Size::new(cross_bounds.width, cross_bounds.height), + size: core::Pixels(close_size + if is_mouse_over_cross { 1.0 } else { 0.0 }), + font: BOOTSTRAP_FONT, + horizontal_alignment: Horizontal::Center, + vertical_alignment: Vertical::Center, + line_height: LineHeight::Relative(1.3), + shaping: iced_widget::text::Shaping::Basic, }, - size: close_size + if is_mouse_over_cross { 1.0 } else { 0.0 }, - color: style.icon_color, - font: BOOTSTRAP_FONT, - horizontal_alignment: Horizontal::Center, - vertical_alignment: Vertical::Center, - line_height: LineHeight::Relative(1.3), - shaping: iced_widget::text::Shaping::Basic, - }); + Point::new(cross_bounds.center_x(), cross_bounds.center_y()), + style.text_color, + cross_bounds, + ); if is_mouse_over_cross { renderer.fill_quad( diff --git a/src/native/tabs.rs b/src/native/tabs.rs index 2e938a50..41227583 100644 --- a/src/native/tabs.rs +++ b/src/native/tabs.rs @@ -15,7 +15,10 @@ use iced_widget::{ layout::{Limits, Node}, mouse::{self, Cursor}, renderer, - widget::{Operation, Tree}, + widget::{ + tree::{State, Tag}, + Operation, Tree, + }, Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, }, runtime::Font, @@ -276,11 +279,23 @@ where TabId: Eq + Clone, { fn children(&self) -> Vec { - self.tabs.iter().map(Tree::new).collect() + let tabs = Tree { + tag: Tag::stateless(), + state: State::None, + children: self.tabs.iter().map(Tree::new).collect(), + }; + + let bar = Tree { + tag: self.tab_bar.tag(), + state: self.tab_bar.state(), + children: self.tab_bar.children(), + }; + + vec![bar, tabs] } fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.tabs); + tree.children[1].diff_children(&self.tabs); } fn width(&self) -> Length { @@ -291,25 +306,27 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let tab_bar_limits = limits.width(self.width).height(Length::Shrink); - - let mut tab_bar_node = self.tab_bar.layout(renderer, &tab_bar_limits); + let mut tab_bar_node = + self.tab_bar + .layout(&mut tree.children[0], renderer, &tab_bar_limits); let tab_content_limits = limits.width(self.width).height(self.height); - let mut tab_content_node = self - .tabs - .get(self.tab_bar.get_active_tab_idx()) - .map_or_else( - || { - Row::::new() - .width(Length::Fill) - .height(Length::Fill) - .layout(renderer, &tab_content_limits) - }, - |element| element.as_widget().layout(renderer, &tab_content_limits), - ); + let mut tab_content_node = + if let Some(element) = self.tabs.get(self.tab_bar.get_active_tab_idx()) { + element.as_widget().layout( + &mut tree.children[1].children[self.tab_bar.get_active_tab_idx()], + renderer, + &tab_content_limits, + ) + } else { + Row::::new() + .width(Length::Fill) + .height(Length::Fill) + .layout(tree, renderer, &tab_content_limits) + }; tab_bar_node.move_to(Point::new( tab_bar_node.bounds().x, @@ -390,7 +407,7 @@ where .get_mut(idx) .map_or(event::Status::Ignored, |element| { element.as_widget_mut().on_event( - &mut state.children[idx], + &mut state.children[1].children[idx], event, tab_content_layout, cursor, @@ -449,7 +466,7 @@ where let idx = self.tab_bar.get_active_tab_idx(); if let Some(element) = self.tabs.get(idx) { let new_mouse_interaction = element.as_widget().mouse_interaction( - &state.children[idx], + &state.children[1].children[idx], tab_content_layout, cursor, viewport, @@ -508,7 +525,7 @@ where let idx = self.tab_bar.get_active_tab_idx(); if let Some(element) = self.tabs.get(idx) { element.as_widget().draw( - &state.children[idx], + &state.children[1].children[idx], renderer, theme, style, @@ -535,7 +552,7 @@ where self.tabs .get_mut(idx) .map(Element::as_widget_mut) - .and_then(|w| w.overlay(&mut state.children[idx], layout, renderer)) + .and_then(|w| w.overlay(&mut state.children[1].children[idx], layout, renderer)) }) } @@ -549,7 +566,7 @@ where let active_tab = self.tab_bar.get_active_tab_idx(); operation.container(None, layout.bounds(), &mut |operation| { self.tabs[active_tab].as_widget().operate( - &mut tree.children[active_tab], + &mut tree.children[1].children[active_tab], layout .children() .nth(1) diff --git a/src/native/time_picker.rs b/src/native/time_picker.rs index a0dfe3df..533d3a41 100644 --- a/src/native/time_picker.rs +++ b/src/native/time_picker.rs @@ -197,8 +197,10 @@ where self.underlay.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.underlay.as_widget().layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.underlay + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn on_event( diff --git a/src/native/wrap.rs b/src/native/wrap.rs index e80a4e75..52a7060f 100644 --- a/src/native/wrap.rs +++ b/src/native/wrap.rs @@ -176,8 +176,8 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { - self.inner_layout(renderer, limits) + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + self.inner_layout(tree, renderer, limits) } fn on_event( @@ -337,7 +337,7 @@ where Renderer: core::Renderer, { /// A inner layout of the [`Wrap`]. - fn inner_layout(&self, renderer: &Renderer, limits: &Limits) -> Node; + fn inner_layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node; } impl<'a, Message, Renderer> WrapLayout @@ -347,7 +347,7 @@ where { #[allow(clippy::inline_always)] #[inline(always)] - fn inner_layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn inner_layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let padding = Padding::from(self.padding); let spacing = self.spacing; let line_spacing = self.line_spacing; @@ -361,6 +361,7 @@ where .max_height(self.max_height); let max_width = limits.max().width; + let mut children = tree.children.iter_mut(); let mut curse = padding.left; let mut deep_curse = padding.left; let mut current_line_height = line_minimal_length; @@ -376,7 +377,11 @@ where Size::new(limits.min().width, line_minimal_length), limits.max(), ); - let mut node = elem.as_widget().layout(renderer, &node_limit); + let mut node = elem.as_widget().layout( + children.next().expect("wrap missing expected child"), + renderer, + &node_limit, + ); let size = node.size(); @@ -429,7 +434,7 @@ where { #[allow(clippy::inline_always)] #[inline(always)] - fn inner_layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn inner_layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let padding = Padding::from(self.padding); let spacing = self.spacing; let line_spacing = self.line_spacing; @@ -443,6 +448,7 @@ where .max_height(self.max_height); let max_height = limits.max().height; + let mut children = tree.children.iter_mut(); let mut curse = padding.left; let mut wide_curse = padding.left; let mut current_line_width = line_minimal_length; @@ -458,7 +464,11 @@ where Size::new(line_minimal_length, limits.min().height), limits.max(), ); - let mut node = elem.as_widget().layout(renderer, &node_limit); + let mut node = elem.as_widget().layout( + children.next().expect("wrap missing expected child"), + renderer, + &node_limit, + ); let size = node.size();