diff --git a/druid/examples/menu.rs b/druid/examples/menu.rs new file mode 100644 index 0000000000..4bd2eb08fa --- /dev/null +++ b/druid/examples/menu.rs @@ -0,0 +1,146 @@ +// 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") + .radio_item(1, Some(0)) + .lens(AppState::value), + ) + .entry( + MenuItem::new("2 Selected") + .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), + ) + .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), + ) + .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 + } + } +} 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 715f0c02dc..f1e9e10325 100644 --- a/druid/src/menu/mod.rs +++ b/druid/src/menu/mod.rs @@ -643,6 +643,25 @@ 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 +743,17 @@ 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 {