From f101a8f5d3402643ce5d320413fb3f931dc494d0 Mon Sep 17 00:00:00 2001 From: xarvic Date: Tue, 20 Sep 2022 21:52:01 +0200 Subject: [PATCH 1/3] implement menu example --- druid/examples/menu.rs | 127 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 druid/examples/menu.rs diff --git a/druid/examples/menu.rs b/druid/examples/menu.rs new file mode 100644 index 0000000000..815283299b --- /dev/null +++ b/druid/examples/menu.rs @@ -0,0 +1,127 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This is a very small example of how to use menus. +//! It does the almost bare minimum while still being useful. + +// On Windows platform, don't show a console when opening the app. +#![windows_subsystem = "windows"] + +use druid::widget::prelude::*; +use druid::widget::{Flex, Label}; +use druid::{AppDelegate, AppLauncher, Command, Data, DelegateCtx, Handled, Lens, Menu, MenuItem, Selector, Target, WidgetExt, WindowDesc}; + +const COMMAND: Selector = Selector::new("custom_Selector"); + +#[derive(Clone, Data, Lens)] +struct AppState { + option: bool, + value: usize, +} + +pub fn main() { + + + // describe the main window + let main_window = WindowDesc::new(build_root_widget()) + .title("Hello World!") + .window_size((400.0, 400.0)) + .menu(|_, _, _|build_menu()); + + // create the initial app state + let initial_state: AppState = AppState { + option: false, + value: 0, + }; + + // start the application. Here we pass in the application state. + AppLauncher::with_window(main_window) + .log_to_console() + .delegate(Delegate) + .launch(initial_state) + .expect("Failed to launch application"); +} + +fn build_root_widget() -> impl Widget { + Flex::column() + .with_child(Label::new(|data: &AppState, _: &_|format!("Current value: {}", data.value))) + .with_default_spacer() + .with_child(Label::new(|data: &AppState, _: &_|format!("IS selected: {}", data.option))) + .center() +} + +fn build_menu() -> Menu { + let menu = Menu::new("Druid Menu") + .entry( + MenuItem::new("Send Command") + .command(COMMAND) + ) + .separator() + .entry( + MenuItem::new("Change value") + .on_activate(|_, data: &mut AppState, _|data.value = (data.value + 1) % 4) + ) + .entry( + MenuItem::new("1 Selected") + .on_activate(|_, data: &mut AppState, _|if data.value == 1 {data.value = 0} else {data.value = 1}) + .selected_if(|data: &AppState, _|data.value == 1) + ) + .entry( + MenuItem::new("2 Selected") + .on_activate(|_, data: &mut AppState, _|if data.value == 2 {data.value = 0} else {data.value = 2}) + .selected_if(|data: &AppState, _|data.value == 2) + ) + .entry( + MenuItem::new("3 Selected") + .on_activate(|_, data: &mut AppState, _|if data.value == 3 {data.value = 0} else {data.value = 3}) + .selected_if(|data: &AppState, _|data.value == 3) + ) + .separator() + .entry( + MenuItem::new("CheckBox") + .on_activate(|_, data: &mut AppState, _|data.option = !data.option) + .selected_if(|data: &AppState, _|data.option) + ) + .entry( + MenuItem::new("Disabled") + .on_activate(|_, _, _|panic!("disabled Menu Item was activated!")) + .enabled(false) + + ) + .entry( + MenuItem::new("Disabled Selectable") + .on_activate(|_, _, _|panic!("disabled Menu Item was activated!")) + .selected(false) + .enabled(false) + ) + //we dont add new menu items based on data! + .rebuild_on(|_, _, _|false); + + Menu::empty() + .entry(menu) + +} + +struct Delegate; + +impl AppDelegate for Delegate { + fn command(&mut self, _: &mut DelegateCtx, _: Target, cmd: &Command, _: &mut AppState, _: &Env) -> Handled { + if cmd.is(COMMAND) { + println!("Clicked \"Send Command\"!"); + Handled::Yes + } else { + Handled::No + } + } +} From 78d4902a0dbe578135bbcbd3d59f26af07a529f0 Mon Sep 17 00:00:00 2001 From: xarvic Date: Tue, 20 Sep 2022 22:14:25 +0200 Subject: [PATCH 2/3] update example --- druid/examples/menu.rs | 15 +++++++++++---- druid/src/menu/mod.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/druid/examples/menu.rs b/druid/examples/menu.rs index 815283299b..910e24d0bf 100644 --- a/druid/examples/menu.rs +++ b/druid/examples/menu.rs @@ -74,15 +74,16 @@ fn build_menu() -> Menu { ) .entry( MenuItem::new("1 Selected") - .on_activate(|_, data: &mut AppState, _|if data.value == 1 {data.value = 0} else {data.value = 1}) - .selected_if(|data: &AppState, _|data.value == 1) + .radio_item(1, Some(0)) + .lens(AppState::value) ) .entry( MenuItem::new("2 Selected") - .on_activate(|_, data: &mut AppState, _|if data.value == 2 {data.value = 0} else {data.value = 2}) - .selected_if(|data: &AppState, _|data.value == 2) + .radio_item(2, Some(0)) + .lens(AppState::value) ) .entry( + // Implementing the radio item from hand MenuItem::new("3 Selected") .on_activate(|_, data: &mut AppState, _|if data.value == 3 {data.value = 0} else {data.value = 3}) .selected_if(|data: &AppState, _|data.value == 3) @@ -90,6 +91,12 @@ fn build_menu() -> Menu { .separator() .entry( MenuItem::new("CheckBox") + .toggle_data() + .lens(AppState::option) + ) + .entry( + // Implementing the CheckBox from hand + MenuItem::new("Manual CheckBox") .on_activate(|_, data: &mut AppState, _|data.option = !data.option) .selected_if(|data: &AppState, _|data.option) ) diff --git a/druid/src/menu/mod.rs b/druid/src/menu/mod.rs index 715f0c02dc..5e155a1453 100644 --- a/druid/src/menu/mod.rs +++ b/druid/src/menu/mod.rs @@ -643,6 +643,23 @@ impl MenuItem { self.on_activate(move |ctx, _data, _env| ctx.submit_command(cmd.clone())) } + /// Turns this `MenuItem` into a `RadioButton` + /// + /// When selected this MenuItem will set the provided value as data. + /// If data is equal to the provided value the Item is selected otherwise not. + pub fn radio_item(self, value: T, unselect: Option) -> Self where T: PartialEq { + let value2 = value.clone(); + self + .on_activate(move |_, data: &mut T, _|{ + if data != value { + *data = value.clone(); + } else if let Some(value) = unselect.clone() { + *data = value; + } + }) + .selected_if(move |data, _|data == value2) + } + /// Provide a hotkey for activating this menu item. /// /// This is equivalent to @@ -724,6 +741,18 @@ impl MenuItem { } } +impl MenuItem { + /// Turns the MenuItem into a CheckBox. + /// + /// this is a convenience method which sets the `on_activate` and `selected_if` callbacks + /// to behave like a `CheckBox`. + pub fn toggle_data(self) -> Self { + self + .on_activate(|_, data, _|*data = !*data) + .selected_if(|data, _|*data) + } +} + impl MenuVisitor for Menu { fn activate(&mut self, ctx: &mut MenuEventCtx, id: MenuItemId, data: &mut T, env: &Env) { for child in &mut self.children { From 422505b073bedde02aa57d41000ba724e8a0e31c Mon Sep 17 00:00:00 2001 From: xarvic Date: Sat, 21 Jan 2023 10:26:35 +0100 Subject: [PATCH 3/3] reformat fix clippy suggestions --- druid/examples/menu.rs | 68 +++++++++++++++++++------------- druid/examples/web/src/lib.rs | 2 + druid/examples/widget_gallery.rs | 4 +- druid/src/menu/mod.rs | 27 +++++++------ 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/druid/examples/menu.rs b/druid/examples/menu.rs index 910e24d0bf..4bd2eb08fa 100644 --- a/druid/examples/menu.rs +++ b/druid/examples/menu.rs @@ -20,7 +20,10 @@ use druid::widget::prelude::*; use druid::widget::{Flex, Label}; -use druid::{AppDelegate, AppLauncher, Command, Data, DelegateCtx, Handled, Lens, Menu, MenuItem, Selector, Target, WidgetExt, WindowDesc}; +use druid::{ + AppDelegate, AppLauncher, Command, Data, DelegateCtx, Handled, Lens, Menu, MenuItem, Selector, + Target, WidgetExt, WindowDesc, +}; const COMMAND: Selector = Selector::new("custom_Selector"); @@ -31,13 +34,11 @@ struct AppState { } pub fn main() { - - // describe the main window let main_window = WindowDesc::new(build_root_widget()) .title("Hello World!") .window_size((400.0, 400.0)) - .menu(|_, _, _|build_menu()); + .menu(|_, _, _| build_menu()); // create the initial app state let initial_state: AppState = AppState { @@ -55,75 +56,86 @@ pub fn main() { fn build_root_widget() -> impl Widget { Flex::column() - .with_child(Label::new(|data: &AppState, _: &_|format!("Current value: {}", data.value))) + .with_child(Label::new(|data: &AppState, _: &_| { + format!("Current value: {}", data.value) + })) .with_default_spacer() - .with_child(Label::new(|data: &AppState, _: &_|format!("IS selected: {}", data.option))) + .with_child(Label::new(|data: &AppState, _: &_| { + format!("IS selected: {}", data.option) + })) .center() } fn build_menu() -> Menu { let menu = Menu::new("Druid Menu") - .entry( - MenuItem::new("Send Command") - .command(COMMAND) - ) + .entry(MenuItem::new("Send Command").command(COMMAND)) .separator() .entry( MenuItem::new("Change value") - .on_activate(|_, data: &mut AppState, _|data.value = (data.value + 1) % 4) + .on_activate(|_, data: &mut AppState, _| data.value = (data.value + 1) % 4), ) .entry( MenuItem::new("1 Selected") .radio_item(1, Some(0)) - .lens(AppState::value) + .lens(AppState::value), ) .entry( MenuItem::new("2 Selected") .radio_item(2, Some(0)) - .lens(AppState::value) + .lens(AppState::value), ) .entry( // Implementing the radio item from hand MenuItem::new("3 Selected") - .on_activate(|_, data: &mut AppState, _|if data.value == 3 {data.value = 0} else {data.value = 3}) - .selected_if(|data: &AppState, _|data.value == 3) + .on_activate(|_, data: &mut AppState, _| { + if data.value == 3 { + data.value = 0 + } else { + data.value = 3 + } + }) + .selected_if(|data: &AppState, _| data.value == 3), ) .separator() .entry( MenuItem::new("CheckBox") .toggle_data() - .lens(AppState::option) + .lens(AppState::option), ) .entry( // Implementing the CheckBox from hand MenuItem::new("Manual CheckBox") - .on_activate(|_, data: &mut AppState, _|data.option = !data.option) - .selected_if(|data: &AppState, _|data.option) + .on_activate(|_, data: &mut AppState, _| data.option = !data.option) + .selected_if(|data: &AppState, _| data.option), ) .entry( MenuItem::new("Disabled") - .on_activate(|_, _, _|panic!("disabled Menu Item was activated!")) - .enabled(false) - + .on_activate(|_, _, _| panic!("disabled Menu Item was activated!")) + .enabled(false), ) .entry( MenuItem::new("Disabled Selectable") - .on_activate(|_, _, _|panic!("disabled Menu Item was activated!")) + .on_activate(|_, _, _| panic!("disabled Menu Item was activated!")) .selected(false) - .enabled(false) + .enabled(false), ) //we dont add new menu items based on data! - .rebuild_on(|_, _, _|false); - - Menu::empty() - .entry(menu) + .rebuild_on(|_, _, _| false); + Menu::empty().entry(menu) } struct Delegate; impl AppDelegate for Delegate { - fn command(&mut self, _: &mut DelegateCtx, _: Target, cmd: &Command, _: &mut AppState, _: &Env) -> Handled { + fn command( + &mut self, + _: &mut DelegateCtx, + _: Target, + cmd: &Command, + _: &mut AppState, + _: &Env, + ) -> Handled { if cmd.is(COMMAND) { println!("Clicked \"Send Command\"!"); Handled::Yes diff --git a/druid/examples/web/src/lib.rs b/druid/examples/web/src/lib.rs index 34f3ec54b6..4f60837ef0 100644 --- a/druid/examples/web/src/lib.rs +++ b/druid/examples/web/src/lib.rs @@ -70,6 +70,7 @@ impl_example!(invalidation); impl_example!(layout); impl_example!(lens); impl_example!(list); +impl_example!(menu); impl_example!(multiwin); impl_example!(open_save); impl_example!(panels.unwrap()); @@ -86,3 +87,4 @@ impl_example!(transparency); impl_example!(view_switcher); impl_example!(widget_gallery); impl_example!(text); +impl_example!(z_stack); diff --git a/druid/examples/widget_gallery.rs b/druid/examples/widget_gallery.rs index ef1515860f..385085dd2c 100644 --- a/druid/examples/widget_gallery.rs +++ b/druid/examples/widget_gallery.rs @@ -333,7 +333,7 @@ impl Widget for SquaresGrid { // The space needed to lay all elements out on a single line. let ideal_width = (self.cell_size.width + self.spacing + 1.0) * count; // Constrain the width. - let width = ideal_width.min(bc.max().width).max(bc.min().width); + let width = ideal_width.clamp(bc.min().width, bc.max().width); // Given the width, the space needed to lay out all elements (as many as possible on each // line). let cells_in_row = @@ -345,7 +345,7 @@ impl Widget for SquaresGrid { let ideal_height = height_from_rows(rows); // Constrain the height - let height = ideal_height.max(bc.min().height).min(bc.max().height); + let height = ideal_height.clamp(bc.min().height, bc.max().height); // Now calculate how many rows we can actually fit in while height_from_rows(rows) > height && rows > 0 { rows -= 1; diff --git a/druid/src/menu/mod.rs b/druid/src/menu/mod.rs index 5e155a1453..f1e9e10325 100644 --- a/druid/src/menu/mod.rs +++ b/druid/src/menu/mod.rs @@ -647,17 +647,19 @@ impl MenuItem { /// /// When selected this MenuItem will set the provided value as data. /// If data is equal to the provided value the Item is selected otherwise not. - pub fn radio_item(self, value: T, unselect: Option) -> Self where T: PartialEq { + pub fn radio_item(self, value: T, unselect: Option) -> Self + where + T: PartialEq, + { let value2 = value.clone(); - self - .on_activate(move |_, data: &mut T, _|{ - if data != value { - *data = value.clone(); - } else if let Some(value) = unselect.clone() { - *data = value; - } - }) - .selected_if(move |data, _|data == value2) + self.on_activate(move |_, data: &mut T, _| { + if *data != value { + *data = value.clone(); + } else if let Some(value) = unselect.clone() { + *data = value; + } + }) + .selected_if(move |data, _| *data == value2) } /// Provide a hotkey for activating this menu item. @@ -747,9 +749,8 @@ impl MenuItem { /// this is a convenience method which sets the `on_activate` and `selected_if` callbacks /// to behave like a `CheckBox`. pub fn toggle_data(self) -> Self { - self - .on_activate(|_, data, _|*data = !*data) - .selected_if(|data, _|*data) + self.on_activate(|_, data, _| *data = !*data) + .selected_if(|data, _| *data) } }