diff options
-rw-r--r-- | examples/checkbox/Cargo.toml | 9 | ||||
-rw-r--r-- | examples/checkbox/README.md | 12 | ||||
-rw-r--r-- | examples/checkbox/fonts/icons.ttf | bin | 0 -> 1272 bytes | |||
-rw-r--r-- | examples/checkbox/src/main.rs | 63 | ||||
-rw-r--r-- | lazy/src/component.rs | 9 | ||||
-rw-r--r-- | lazy/src/lazy.rs | 7 | ||||
-rw-r--r-- | lazy/src/responsive.rs | 7 | ||||
-rw-r--r-- | native/src/user_interface.rs | 18 | ||||
-rw-r--r-- | native/src/widget/checkbox.rs | 38 | ||||
-rw-r--r-- | native/src/widget/container.rs | 53 | ||||
-rw-r--r-- | native/src/widget/pick_list.rs | 131 | ||||
-rw-r--r-- | native/src/widget/scrollable.rs | 19 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 2 | ||||
-rw-r--r-- | src/widget.rs | 6 | ||||
-rw-r--r-- | style/src/checkbox.rs | 4 | ||||
-rw-r--r-- | style/src/theme.rs | 4 | ||||
-rw-r--r-- | winit/src/application.rs | 20 | ||||
-rw-r--r-- | winit/src/settings.rs | 3 |
18 files changed, 299 insertions, 106 deletions
diff --git a/examples/checkbox/Cargo.toml b/examples/checkbox/Cargo.toml new file mode 100644 index 00000000..dde8f910 --- /dev/null +++ b/examples/checkbox/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "checkbox" +version = "0.1.0" +authors = ["Casper Rogild Storm<casper@rogildstorm.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/checkbox/README.md b/examples/checkbox/README.md new file mode 100644 index 00000000..b7f85684 --- /dev/null +++ b/examples/checkbox/README.md @@ -0,0 +1,12 @@ +## Checkbox + +A box that can be checked. + +The __[`main`]__ file contains all the code of the example. + +You can run it with `cargo run`: +``` +cargo run --package pick_list +``` + +[`main`]: src/main.rs diff --git a/examples/checkbox/fonts/icons.ttf b/examples/checkbox/fonts/icons.ttf Binary files differnew file mode 100644 index 00000000..a2046844 --- /dev/null +++ b/examples/checkbox/fonts/icons.ttf diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs new file mode 100644 index 00000000..09950bb8 --- /dev/null +++ b/examples/checkbox/src/main.rs @@ -0,0 +1,63 @@ +use iced::widget::{checkbox, column, container}; +use iced::{Element, Font, Length, Sandbox, Settings}; + +const ICON_FONT: Font = Font::External { + name: "Icons", + bytes: include_bytes!("../fonts/icons.ttf"), +}; + +pub fn main() -> iced::Result { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + default_checkbox: bool, + custom_checkbox: bool, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + DefaultChecked(bool), + CustomChecked(bool), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Default::default() + } + + fn title(&self) -> String { + String::from("Checkbox - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::DefaultChecked(value) => self.default_checkbox = value, + Message::CustomChecked(value) => self.custom_checkbox = value, + } + } + + fn view(&self) -> Element<Message> { + let default_checkbox = + checkbox("Default", self.default_checkbox, Message::DefaultChecked); + let custom_checkbox = + checkbox("Custom", self.custom_checkbox, Message::CustomChecked) + .icon(checkbox::Icon { + font: ICON_FONT, + code_point: '\u{e901}', + size: None, + }); + + let content = column![default_checkbox, custom_checkbox].spacing(22); + + container(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 94263274..b23da9f7 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -311,6 +311,8 @@ where } self.with_element(|element| { + tree.diff_children(std::slice::from_ref(&element)); + element.as_widget().operate( &mut tree.children[0], layout, @@ -563,4 +565,11 @@ where event_status } + + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.with_overlay_maybe(|overlay| { + overlay.is_over(layout, cursor_position) + }) + .unwrap_or_default() + } } diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index 9795afa4..5e909a49 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -372,6 +372,13 @@ where }) .unwrap_or(iced_native::event::Status::Ignored) } + + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.with_overlay_maybe(|overlay| { + overlay.is_over(layout, cursor_position) + }) + .unwrap_or_default() + } } impl<'a, Message, Renderer, Dependency, View> diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index e399e7b0..93069493 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -415,4 +415,11 @@ where }) .unwrap_or(iced_native::event::Status::Ignored) } + + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.with_overlay_maybe(|overlay| { + overlay.is_over(layout, cursor_position) + }) + .unwrap_or_default() + } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 80dece21..2358bff1 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -263,16 +263,16 @@ where } } - let base_cursor = if manual_overlay + let base_cursor = manual_overlay .as_ref() - .unwrap() - .is_over(Layout::new(&layout), cursor_position) - { - // TODO: Type-safe cursor availability - Point::new(-1.0, -1.0) - } else { - cursor_position - }; + .filter(|overlay| { + overlay.is_over(Layout::new(&layout), cursor_position) + }) + .map(|_| { + // TODO: Type-safe cursor availability + Point::new(-1.0, -1.0) + }) + .unwrap_or(cursor_position); self.overlay = Some(layout); diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index b46433c2..f6298a8c 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -14,6 +14,17 @@ use crate::{ pub use iced_style::checkbox::{Appearance, StyleSheet}; +/// The icon in a [`Checkbox`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Icon<Font> { + /// Font that will be used to display the `code_point`, + pub font: Font, + /// The unicode code point that will be used as the icon. + pub code_point: char, + /// Font size of the content. + pub size: Option<u16>, +} + /// A box that can be checked. /// /// # Example @@ -45,6 +56,7 @@ where spacing: u16, text_size: Option<u16>, font: Renderer::Font, + icon: Icon<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -80,6 +92,11 @@ where spacing: Self::DEFAULT_SPACING, text_size: None, font: Renderer::Font::default(), + icon: Icon { + font: Renderer::ICON_FONT, + code_point: Renderer::CHECKMARK_ICON, + size: None, + }, style: Default::default(), } } @@ -116,6 +133,12 @@ where self } + /// Sets the [`Icon`] of the [`Checkbox`]. + pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self { + self.icon = icon; + self + } + /// Sets the style of the [`Checkbox`]. pub fn style( mut self, @@ -243,17 +266,24 @@ where custom_style.background, ); + let Icon { + font, + code_point, + size, + } = &self.icon; + let size = size.map(f32::from).unwrap_or(bounds.height * 0.7); + if self.is_checked { renderer.fill_text(text::Text { - content: &Renderer::CHECKMARK_ICON.to_string(), - font: Renderer::ICON_FONT, - size: bounds.height * 0.7, + content: &code_point.to_string(), + font: font.clone(), + size, bounds: Rectangle { x: bounds.center_x(), y: bounds.center_y(), ..bounds }, - color: custom_style.checkmark_color, + color: custom_style.icon_color, horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, }); diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index cdf1c859..c82b8be2 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -5,7 +5,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::widget::{Operation, Tree}; +use crate::widget::{self, Operation, Tree}; use crate::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Widget, @@ -24,6 +24,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { + id: Option<Id>, padding: Padding, width: Length, height: Length, @@ -46,6 +47,7 @@ where T: Into<Element<'a, Message, Renderer>>, { Container { + id: None, padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, @@ -58,6 +60,12 @@ where } } + /// Sets the [`Id`] of the [`Container`]. + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + /// Sets the [`Padding`] of the [`Container`]. pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { self.padding = padding.into(); @@ -172,14 +180,17 @@ where renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { - operation.container(None, &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }); + operation.container( + self.id.as_ref().map(|id| &id.0), + &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }, + ); } fn on_event( @@ -333,3 +344,27 @@ pub fn draw_background<Renderer>( ); } } + +/// The identifier of a [`Container`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(widget::Id); + +impl Id { + /// Creates a custom [`Id`]. + pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { + Self(widget::Id::new(id)) + } + + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. + pub fn unique() -> Self { + Self(widget::Id::unique()) + } +} + +impl From<Id> for widget::Id { + fn from(id: Id) -> Self { + id.0 + } +} diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index c2853314..b1cdfad4 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -20,60 +20,6 @@ use std::borrow::Cow; pub use iced_style::pick_list::{Appearance, StyleSheet}; -/// The handle to the right side of the [`PickList`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Handle<Renderer> -where - Renderer: text::Renderer, -{ - /// Displays an arrow icon (▼). - /// - /// This is the default. - Arrow { - /// Font size of the content. - size: Option<u16>, - }, - /// A custom handle. - Custom { - /// Font that will be used to display the `text`, - font: Renderer::Font, - /// Text that will be shown. - text: String, - /// Font size of the content. - size: Option<u16>, - }, - /// No handle will be shown. - None, -} - -impl<Renderer> Default for Handle<Renderer> -where - Renderer: text::Renderer, -{ - fn default() -> Self { - Self::Arrow { size: None } - } -} - -impl<Renderer> Handle<Renderer> -where - Renderer: text::Renderer, -{ - fn content(&self) -> Option<(Renderer::Font, String, Option<u16>)> { - match self { - Self::Arrow { size } => Some(( - Renderer::ICON_FONT, - Renderer::ARROW_DOWN_ICON.to_string(), - *size, - )), - Self::Custom { font, text, size } => { - Some((font.clone(), text.clone(), *size)) - } - Self::None => None, - } - } -} - /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] pub struct PickList<'a, T, Message, Renderer> @@ -90,7 +36,7 @@ where padding: Padding, text_size: Option<u16>, font: Renderer::Font, - handle: Handle<Renderer>, + handle: Handle<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -161,7 +107,7 @@ where } /// Sets the [`Handle`] of the [`PickList`]. - pub fn handle(mut self, handle: Handle<Renderer>) -> Self { + pub fn handle(mut self, handle: Handle<Renderer::Font>) -> Self { self.handle = handle; self } @@ -258,7 +204,7 @@ where fn draw( &self, - _tree: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, _style: &renderer::Style, @@ -278,6 +224,7 @@ where self.selected.as_ref(), &self.handle, &self.style, + || tree.state.downcast_ref::<State<T>>(), ) } @@ -349,6 +296,46 @@ impl<T> Default for State<T> { } } +/// The handle to the right side of the [`PickList`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Handle<Font> { + /// Displays an arrow icon (▼). + /// + /// This is the default. + Arrow { + /// Font size of the content. + size: Option<u16>, + }, + /// A custom static handle. + Static(Icon<Font>), + /// A custom dynamic handle. + Dynamic { + /// The [`Icon`] used when [`PickList`] is closed. + closed: Icon<Font>, + /// The [`Icon`] used when [`PickList`] is open. + open: Icon<Font>, + }, + /// No handle will be shown. + None, +} + +impl<Font> Default for Handle<Font> { + fn default() -> Self { + Self::Arrow { size: None } + } +} + +/// The icon of a [`Handle`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Icon<Font> { + /// Font that will be used to display the `code_point`, + pub font: Font, + /// The unicode code point that will be used as the icon. + pub code_point: char, + /// Font size of the content. + pub size: Option<u16>, +} + /// Computes the layout of a [`PickList`]. pub fn layout<Renderer, T>( renderer: &Renderer, @@ -568,7 +555,7 @@ where } /// Draws a [`PickList`]. -pub fn draw<T, Renderer>( +pub fn draw<'a, T, Renderer>( renderer: &mut Renderer, theme: &Renderer::Theme, layout: Layout<'_>, @@ -578,12 +565,13 @@ pub fn draw<T, Renderer>( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, - handle: &Handle<Renderer>, + handle: &Handle<Renderer::Font>, style: &<Renderer::Theme as StyleSheet>::Style, + state: impl FnOnce() -> &'a State<T>, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, - T: ToString, + T: ToString + 'a, { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); @@ -605,11 +593,30 @@ pub fn draw<T, Renderer>( style.background, ); - if let Some((font, text, size)) = handle.content() { + let handle = match handle { + Handle::Arrow { size } => { + Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) + } + Handle::Static(Icon { + font, + code_point, + size, + }) => Some((font.clone(), *code_point, *size)), + Handle::Dynamic { open, closed } => { + if state().is_open { + Some((open.font.clone(), open.code_point, open.size)) + } else { + Some((closed.font.clone(), closed.code_point, closed.size)) + } + } + Handle::None => None, + }; + + if let Some((font, code_point, size)) = handle { let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); renderer.fill_text(Text { - content: &text, + content: &code_point.to_string(), size, font, color: style.handle_color, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 82286036..2de722e4 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -208,14 +208,17 @@ where operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); - operation.container(None, &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }); + operation.container( + self.id.as_ref().map(|id| &id.0), + &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }, + ); } fn on_event( diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 8755b85d..5bfc918c 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -389,8 +389,8 @@ where let padding = padding.fit(Size::ZERO, limits.max()); let limits = limits - .pad(padding) .width(width) + .pad(padding) .height(Length::Units(text_size)); let mut text = layout::Node::new(limits.resolve(Size::ZERO)); diff --git a/src/widget.rs b/src/widget.rs index f0058f57..7da5b82b 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -31,7 +31,7 @@ pub mod button { pub mod checkbox { //! Show toggle controls using checkboxes. - pub use iced_native::widget::checkbox::{Appearance, StyleSheet}; + pub use iced_native::widget::checkbox::{Appearance, Icon, StyleSheet}; /// A box that can be checked. pub type Checkbox<'a, Message, Renderer = crate::Renderer> = @@ -80,7 +80,9 @@ pub mod pane_grid { pub mod pick_list { //! Display a dropdown list of selectable values. - pub use iced_native::widget::pick_list::{Appearance, Handle, StyleSheet}; + pub use iced_native::widget::pick_list::{ + Appearance, Handle, Icon, StyleSheet, + }; /// A widget allowing the selection of a single value from a list of options. pub type PickList<'a, T, Message, Renderer = crate::Renderer> = diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs index 827b3225..52b90ec9 100644 --- a/style/src/checkbox.rs +++ b/style/src/checkbox.rs @@ -6,8 +6,8 @@ use iced_core::{Background, Color}; pub struct Appearance { /// The [`Background`] of the checkbox. pub background: Background, - /// The checkmark [`Color`] of the checkbox. - pub checkmark_color: Color, + /// The icon [`Color`] of the checkbox. + pub icon_color: Color, /// The border radius of the checkbox. pub border_radius: f32, /// The border width of the checkbox. diff --git a/style/src/theme.rs b/style/src/theme.rs index 55bfa4ca..4ba4facf 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -320,7 +320,7 @@ impl checkbox::StyleSheet for Theme { } fn checkbox_appearance( - checkmark_color: Color, + icon_color: Color, base: palette::Pair, accent: palette::Pair, is_checked: bool, @@ -331,7 +331,7 @@ fn checkbox_appearance( } else { base.color }), - checkmark_color, + icon_color, border_radius: 2.0, border_width: 1.0, border_color: accent.color, diff --git a/winit/src/application.rs b/winit/src/application.rs index c1836ed9..769fe9dd 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -147,11 +147,15 @@ where #[cfg(target_arch = "wasm32")] let target = settings.window.platform_specific.target.clone(); - let builder = settings.window.into_builder( - &application.title(), - event_loop.primary_monitor(), - settings.id, - ); + let should_be_visible = settings.window.visible; + let builder = settings + .window + .into_builder( + &application.title(), + event_loop.primary_monitor(), + settings.id, + ) + .with_visible(false); log::info!("Window builder: {:#?}", builder); @@ -202,6 +206,7 @@ where control_sender, init_command, window, + should_be_visible, settings.exit_on_close_request, ); @@ -268,6 +273,7 @@ async fn run_instance<A, E, C>( mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>, init_command: Command<A::Message>, window: winit::window::Window, + should_be_visible: bool, exit_on_close_request: bool, ) where A: Application + 'static, @@ -295,6 +301,10 @@ async fn run_instance<A, E, C>( physical_size.height, ); + if should_be_visible { + window.set_visible(true); + } + run_command( &application, &mut cache, diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 9bbdef5c..45f38833 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -114,8 +114,7 @@ impl Window { .with_decorations(self.decorations) .with_transparent(self.transparent) .with_window_icon(self.icon) - .with_always_on_top(self.always_on_top) - .with_visible(self.visible); + .with_always_on_top(self.always_on_top); if let Some(position) = conversion::position( primary_monitor.as_ref(), |