Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Menus that use data #1625

Merged
merged 4 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ You can find its changes [documented below](#070---2021-01-01).
- Switch to trace-based logging ([#1562] by [@PoignardAzur])
- Spacers in `Flex` are now implemented by calculating the space in `Flex` instead of creating a widget for it ([#1584] by [@JAicewizard])
- Padding is generic over child widget, impls WidgetWrapper ([#1634] by [@cmyr])
- Menu support was rewritten with support for `Data` ([#1625] by [@jneem])

### Deprecated

Expand Down Expand Up @@ -638,6 +639,7 @@ Last release without a changelog :(
[#1600]: https://github.com/linebender/druid/pull/1600
[#1606]: https://github.com/linebender/druid/pull/1606
[#1619]: https://github.com/linebender/druid/pull/1619
[#1625]: https://github.com/linebender/druid/pull/1625
[#1634]: https://github.com/linebender/druid/pull/1634
[#1635]: https://github.com/linebender/druid/pull/1635
[#1636]: https://github.com/linebender/druid/pull/1636
Expand Down
11 changes: 10 additions & 1 deletion druid-shell/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl Application {
self.platform_app.quit()
}

// TODO: do these two go in some kind of PlatformExt trait?
// TODO: do these three go in some kind of PlatformExt trait?
/// Hide the application this window belongs to. (cmd+H)
pub fn hide(&self) {
#[cfg(target_os = "macos")]
Expand All @@ -182,6 +182,15 @@ impl Application {
self.platform_app.hide_others()
}

/// Sets the global application menu, on platforms where there is one.
///
/// On platforms with no global application menu, this has no effect.
#[allow(unused_variables)]
pub fn set_menu(&self, menu: crate::Menu) {
#[cfg(target_os = "macos")]
self.platform_app.set_menu(menu.into_inner());
}

/// Returns a handle to the system clipboard.
pub fn clipboard(&self) -> Clipboard {
self.platform_app.clipboard().into()
Expand Down
4 changes: 2 additions & 2 deletions druid-shell/src/hotkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use crate::{IntoKey, KbKey, KeyEvent, Modifiers};
/// ```
///
/// [`SysMods`]: enum.SysMods.html
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct HotKey {
pub(crate) mods: RawMods,
pub(crate) key: KbKey,
Expand Down Expand Up @@ -140,7 +140,7 @@ pub enum SysMods {
/// A representation of the active modifier keys.
///
/// This is intended to be clearer than `Modifiers`, when describing hotkeys.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RawMods {
None,
Alt,
Expand Down
3 changes: 2 additions & 1 deletion druid-shell/src/platform/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,8 @@ impl WindowHandle {
.unwrap();

let first_child = &vbox.get_children()[0];
if first_child.is::<gtk::MenuBar>() {
if let Some(old_menubar) = first_child.downcast_ref::<gtk::MenuBar>() {
old_menubar.deactivate();
vbox.remove(first_child);
}
let menubar = menu.into_gtk_menubar(&self, &accel_group);
Expand Down
7 changes: 7 additions & 0 deletions druid-shell/src/platform/mac/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use crate::application::AppHandler;

use super::clipboard::Clipboard;
use super::error::Error;
use super::menu::Menu;
use super::util;

static APP_HANDLER_IVAR: &str = "druidAppHandler";
Expand Down Expand Up @@ -117,6 +118,12 @@ impl Application {
}
}

pub fn set_menu(&self, menu: Menu) {
unsafe {
NSApp().setMainMenu_(menu.menu);
}
}

pub fn clipboard(&self) -> Clipboard {
Clipboard
}
Expand Down
29 changes: 15 additions & 14 deletions druid/examples/markdown_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use druid::widget::prelude::*;
use druid::widget::{Controller, LineBreaking, RawLabel, Scroll, Split, TextBox};
use druid::{
AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, FontFamily, FontStyle, FontWeight,
Handled, Lens, LocalizedString, MenuDesc, Selector, Target, Widget, WidgetExt, WindowDesc,
Handled, Lens, LocalizedString, Menu, Selector, Target, Widget, WidgetExt, WindowDesc,
WindowId,
};

const WINDOW_TITLE: LocalizedString<AppState> = LocalizedString::new("Minimal Markdown");
Expand Down Expand Up @@ -93,7 +94,7 @@ pub fn main() {
// describe the main window
let main_window = WindowDesc::new(build_root_widget())
.title(WINDOW_TITLE)
.menu(make_menu())
.menu(make_menu)
.window_size((700.0, 600.0));

// create the initial app state
Expand Down Expand Up @@ -228,23 +229,23 @@ fn add_attribute_for_tag(tag: &Tag, mut attrs: AttributesAdder) {
}

#[allow(unused_assignments, unused_mut)]
fn make_menu<T: Data>() -> MenuDesc<T> {
let mut base = MenuDesc::empty();
fn make_menu<T: Data>(_window_id: Option<WindowId>, _app_state: &AppState, _env: &Env) -> Menu<T> {
let mut base = Menu::empty();
#[cfg(target_os = "macos")]
{
base = base.append(druid::platform_menus::mac::application::default())
base = base.entry(druid::platform_menus::mac::application::default())
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
base = base.append(druid::platform_menus::win::file::default());
base = base.entry(druid::platform_menus::win::file::default());
}
base.append(
MenuDesc::new(LocalizedString::new("common-menu-edit-menu"))
.append(druid::platform_menus::common::undo())
.append(druid::platform_menus::common::redo())
.append_separator()
.append(druid::platform_menus::common::cut().disabled())
.append(druid::platform_menus::common::copy())
.append(druid::platform_menus::common::paste()),
base.entry(
Menu::new(LocalizedString::new("common-menu-edit-menu"))
.entry(druid::platform_menus::common::undo())
.entry(druid::platform_menus::common::redo())
.separator()
.entry(druid::platform_menus::common::cut().enabled(false))
.entry(druid::platform_menus::common::copy())
.entry(druid::platform_menus::common::paste()),
)
}
149 changes: 59 additions & 90 deletions druid/examples/multiwin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,11 @@ use druid::widget::{
};
use druid::Target::Global;
use druid::{
commands as sys_cmds, AppDelegate, AppLauncher, Application, Color, Command, ContextMenu, Data,
DelegateCtx, Handled, LocalizedString, MenuDesc, MenuItem, Selector, Target, WindowDesc,
WindowId,
commands as sys_cmds, AppDelegate, AppLauncher, Application, Color, Command, Data, DelegateCtx,
Handled, LocalizedString, Menu, MenuItem, Target, WindowDesc, WindowId,
};
use tracing::info;

const MENU_COUNT_ACTION: Selector<usize> = Selector::new("menu-count-action");
const MENU_INCREMENT_ACTION: Selector = Selector::new("menu-increment-action");
const MENU_DECREMENT_ACTION: Selector = Selector::new("menu-decrement-action");
const MENU_SWITCH_GLOW_ACTION: Selector = Selector::new("menu-switch-glow");

#[derive(Debug, Clone, Default, Data)]
struct State {
menu_count: usize,
Expand All @@ -39,11 +33,9 @@ struct State {
}

pub fn main() {
let main_window = WindowDesc::new(ui_builder())
.menu(make_menu(&State::default()))
.title(
LocalizedString::new("multiwin-demo-window-title").with_placeholder("Many windows!"),
);
let main_window = WindowDesc::new(ui_builder()).menu(make_menu).title(
LocalizedString::new("multiwin-demo-window-title").with_placeholder("Many windows!"),
);
AppLauncher::with_window(main_window)
.delegate(Delegate {
windows: Vec::new(),
Expand All @@ -57,10 +49,10 @@ fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
let label = Label::new(text);
let inc_button = Button::<State>::new("Add menu item")
.on_click(|ctx, _data, _env| ctx.submit_command(MENU_INCREMENT_ACTION.to(Global)));
let inc_button =
Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
let dec_button = Button::<State>::new("Remove menu item")
.on_click(|ctx, _data, _env| ctx.submit_command(MENU_DECREMENT_ACTION.to(Global)));
.on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
});
Expand Down Expand Up @@ -134,12 +126,18 @@ struct Delegate {
windows: Vec<WindowId>,
}

impl<T, W: Widget<T>> Controller<T, W> for ContextMenuController {
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
impl<W: Widget<State>> Controller<State, W> for ContextMenuController {
fn event(
&mut self,
child: &mut W,
ctx: &mut EventCtx,
event: &Event,
data: &mut State,
env: &Env,
) {
match event {
Event::MouseDown(ref mouse) if mouse.button.is_right() => {
let menu = ContextMenu::new(make_context_menu::<State>(), mouse.pos);
ctx.show_context_menu(menu);
ctx.show_context_menu(make_context_menu(), mouse.pos);
}
_ => child.event(ctx, event, data, env),
}
Expand All @@ -155,45 +153,14 @@ impl AppDelegate<State> for Delegate {
data: &mut State,
_env: &Env,
) -> Handled {
match cmd {
_ if cmd.is(sys_cmds::NEW_FILE) => {
let new_win = WindowDesc::new(ui_builder())
.menu(make_menu(data))
.window_size((data.selected as f64 * 100.0 + 300.0, 500.0));
ctx.new_window(new_win);
Handled::Yes
}
_ if cmd.is(MENU_COUNT_ACTION) => {
data.selected = *cmd.get_unchecked(MENU_COUNT_ACTION);
let menu = make_menu::<State>(data);
for id in &self.windows {
ctx.set_menu(menu.clone(), *id);
}
Handled::Yes
}
// wouldn't it be nice if a menu (like a button) could just mutate state
// directly if desired?
_ if cmd.is(MENU_INCREMENT_ACTION) => {
data.menu_count += 1;
let menu = make_menu::<State>(data);
for id in &self.windows {
ctx.set_menu(menu.clone(), *id);
}
Handled::Yes
}
_ if cmd.is(MENU_DECREMENT_ACTION) => {
data.menu_count = data.menu_count.saturating_sub(1);
let menu = make_menu::<State>(data);
for id in &self.windows {
ctx.set_menu(menu.clone(), *id);
}
Handled::Yes
}
_ if cmd.is(MENU_SWITCH_GLOW_ACTION) => {
data.glow_hot = !data.glow_hot;
Handled::Yes
}
_ => Handled::No,
if cmd.is(sys_cmds::NEW_FILE) {
let new_win = WindowDesc::new(ui_builder())
.menu(make_menu)
.window_size((data.selected as f64 * 100.0 + 300.0, 500.0));
ctx.new_window(new_win);
Handled::Yes
} else {
Handled::No
}
}

Expand Down Expand Up @@ -223,46 +190,48 @@ impl AppDelegate<State> for Delegate {
}

#[allow(unused_assignments)]
fn make_menu<T: Data>(state: &State) -> MenuDesc<T> {
let mut base = MenuDesc::empty();
fn make_menu(_: Option<WindowId>, state: &State, _: &Env) -> Menu<State> {
let mut base = Menu::empty();
#[cfg(target_os = "macos")]
{
base = druid::platform_menus::mac::menu_bar();
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
base = base.append(druid::platform_menus::win::file::default());
base = base.entry(druid::platform_menus::win::file::default());
}
if state.menu_count != 0 {
base = base.append(
MenuDesc::new(LocalizedString::new("Custom")).append_iter(|| {
(1..state.menu_count + 1).map(|i| {
MenuItem::new(
LocalizedString::new("hello-counter")
.with_arg("count", move |_, _| i.into()),
MENU_COUNT_ACTION.with(i),
)
.disabled_if(|| i % 3 == 0)
.selected_if(|| i == state.selected)
})
}),
);
let mut custom = Menu::new(LocalizedString::new("Custom"));

for i in 1..=state.menu_count {
custom = custom.entry(
MenuItem::new(
LocalizedString::new("hello-counter")
.with_arg("count", move |_: &State, _| i.into()),
)
.on_activate(move |_ctx, data, _env| data.selected = i)
.enabled_if(move |_data, _env| i % 3 != 0)
.selected_if(move |data, _env| i == data.selected),
);
}
base = base.entry(custom);
}
base
base.rebuild_on(|old_data, data, _env| old_data.menu_count != data.menu_count)
}

fn make_context_menu<T: Data>() -> MenuDesc<T> {
MenuDesc::empty()
.append(MenuItem::new(
LocalizedString::new("Increment"),
MENU_INCREMENT_ACTION,
))
.append(MenuItem::new(
LocalizedString::new("Decrement"),
MENU_DECREMENT_ACTION,
))
.append(MenuItem::new(
LocalizedString::new("Glow when hot"),
MENU_SWITCH_GLOW_ACTION,
))
fn make_context_menu() -> Menu<State> {
Menu::empty()
.entry(
MenuItem::new(LocalizedString::new("Increment"))
.on_activate(|_ctx, data: &mut State, _env| data.menu_count += 1),
)
.entry(
MenuItem::new(LocalizedString::new("Decrement")).on_activate(
|_ctx, data: &mut State, _env| data.menu_count = data.menu_count.saturating_sub(1),
),
)
.entry(
MenuItem::new(LocalizedString::new("Glow when hot"))
.on_activate(|_ctx, data: &mut State, _env| data.glow_hot = !data.glow_hot),
)
}
Loading