Skip to content

Commit

Permalink
Replace generic commands with methods on EventCtx and DelegateCtx (#931)
Browse files Browse the repository at this point in the history
  • Loading branch information
luleyleo authored May 15, 2020
1 parent 3f4613e commit 0acb07b
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
- `Event::Wheel` now contains a `MouseEvent` structure. ([#895] by [@teddemunnik])
- `AppDelegate::command` now receives a `Target` instead of a `&Target`. ([#909] by [@xStrom])
- `SHOW_WINDOW` and `CLOSE_WINDOW` commands now only use `Target` to determine the affected window. ([#928] by [@finnerale])
- Replaced `NEW_WINDOW`, `SET_MENU` and `SHOW_CONTEXT_MENU` commands with methods on `EventCtx` and `DelegateCtx`. ([#931] by [@finnerale])

### Deprecated

Expand Down Expand Up @@ -187,6 +188,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
[#925]: https://github.com/xi-editor/druid/pull/925
[#928]: https://github.com/xi-editor/druid/pull/928
[#930]: https://github.com/xi-editor/druid/pull/930
[#931]: https://github.com/xi-editor/druid/pull/931
[#940]: https://github.com/xi-editor/druid/pull/940
[#942]: https://github.com/xi-editor/druid/pull/942
[#943]: https://github.com/xi-editor/druid/pull/943
Expand Down
57 changes: 19 additions & 38 deletions druid/examples/multiwin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
//! Opening and closing windows and using window and context menus.
use druid::widget::prelude::*;
use druid::widget::{Align, BackgroundBrush, Button, Flex, Label, Padding};
use druid::widget::{
Align, BackgroundBrush, Button, Controller, ControllerHost, Flex, Label, Padding,
};
use druid::{
commands as sys_cmds, AppDelegate, AppLauncher, Application, Color, Command, ContextMenu, Data,
DelegateCtx, LocalizedString, MenuDesc, MenuItem, Selector, Target, WindowDesc, WindowId,
Expand Down Expand Up @@ -49,19 +51,6 @@ pub fn main() {
.expect("launch failed");
}

// this is just an experiment for how we might reduce boilerplate.
trait EventCtxExt {
fn set_menu<T: 'static>(&mut self, menu: MenuDesc<T>);
}

impl EventCtxExt for EventCtx<'_> {
fn set_menu<T: 'static>(&mut self, menu: MenuDesc<T>) {
let cmd = Command::new(druid::commands::SET_MENU, menu);
let target = self.window_id();
self.submit_command(cmd, target);
}
}

fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
Expand Down Expand Up @@ -91,7 +80,8 @@ fn ui_builder() -> impl Widget<State> {
row.add_child(Padding::new(5.0, new_button));
row.add_child(Padding::new(5.0, quit_button));
col.add_flex_child(Align::centered(row), 1.0);
Glow::new(col)
let content = ControllerHost::new(col, ContextMenuController);
Glow::new(content)
}

struct Glow<W> {
Expand Down Expand Up @@ -141,28 +131,23 @@ impl<W: Widget<State>> Widget<State> for Glow<W> {
}
}

struct Delegate;
struct ContextMenuController;

impl AppDelegate<State> for Delegate {
fn event(
&mut self,
ctx: &mut DelegateCtx,
window_id: WindowId,
event: Event,
_data: &mut State,
_env: &Env,
) -> Option<Event> {
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) {
match event {
Event::MouseDown(ref mouse) if mouse.button.is_right() => {
let menu = ContextMenu::new(make_context_menu::<State>(), mouse.pos);
let cmd = Command::new(druid::commands::SHOW_CONTEXT_MENU, menu);
ctx.submit_command(cmd, Target::Window(window_id));
None
ctx.show_context_menu(menu);
}
other => Some(other),
_ => child.event(ctx, event, data, env),
}
}
}

struct Delegate;

impl AppDelegate<State> for Delegate {
fn command(
&mut self,
ctx: &mut DelegateCtx,
Expand All @@ -173,34 +158,30 @@ impl AppDelegate<State> for Delegate {
) -> bool {
match (target, &cmd.selector) {
(_, &sys_cmds::NEW_FILE) => {
let new_win = WindowDesc::new(ui_builder)
let window = WindowDesc::new(ui_builder)
.menu(make_menu(data))
.window_size((data.selected as f64 * 100.0 + 300.0, 500.0));
let command = Command::one_shot(sys_cmds::NEW_WINDOW, new_win);
ctx.submit_command(command, Target::Global);
ctx.new_window(window);
false
}
(Target::Window(id), &MENU_COUNT_ACTION) => {
data.selected = *cmd.get_object().unwrap();
let menu = make_menu::<State>(data);
let cmd = Command::new(druid::commands::SET_MENU, menu);
ctx.submit_command(cmd, id);
ctx.set_menu(menu, id);
false
}
// wouldn't it be nice if a menu (like a button) could just mutate state
// directly if desired?
(Target::Window(id), &MENU_INCREMENT_ACTION) => {
data.menu_count += 1;
let menu = make_menu::<State>(data);
let cmd = Command::new(druid::commands::SET_MENU, menu);
ctx.submit_command(cmd, id);
ctx.set_menu(menu, id);
false
}
(Target::Window(id), &MENU_DECREMENT_ACTION) => {
data.menu_count = data.menu_count.saturating_sub(1);
let menu = make_menu::<State>(data);
let cmd = Command::new(druid::commands::SET_MENU, menu);
ctx.submit_command(cmd, id);
ctx.set_menu(menu, id);
false
}
(_, &MENU_SWITCH_GLOW_ACTION) => {
Expand Down
51 changes: 49 additions & 2 deletions druid/src/app_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@

//! Customizing application-level behaviour.
use std::collections::VecDeque;
use std::{
any::{Any, TypeId},
collections::VecDeque,
};

use crate::{Command, Data, Env, Event, Target, WindowId};
use crate::{
commands, contexts::StateTypes, Command, Data, Env, Event, MenuDesc, Target, WindowDesc,
WindowId,
};

/// A context passed in to [`AppDelegate`] functions.
///
/// [`AppDelegate`]: trait.AppDelegate.html
pub struct DelegateCtx<'a> {
pub(crate) command_queue: &'a mut VecDeque<(Target, Command)>,
pub(crate) state_types: StateTypes,
}

impl<'a> DelegateCtx<'a> {
Expand All @@ -43,6 +50,46 @@ impl<'a> DelegateCtx<'a> {
let target = target.into().unwrap_or(Target::Global);
self.command_queue.push_back((target, command))
}

/// Create a new window.
/// `T` must be the application's root `Data` type (the type provided to [`AppLauncher::launch`]).
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>) {
if self.state_types.window_desc == TypeId::of::<WindowDesc<T>>() {
self.submit_command(
Command::one_shot(commands::NEW_WINDOW, desc),
Target::Global,
);
} else {
const MSG: &str = "WindowDesc<T> - T must match the application state.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("DelegateCtx::new_window: {}", MSG)
}
}
}

/// Set the window's menu.
/// `T` must be the application's root `Data` type (the type provided to [`AppLauncher::launch`]).
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>, window: WindowId) {
if self.state_types.menu_desc == TypeId::of::<MenuDesc<T>>() {
self.submit_command(
Command::new(commands::SET_MENU, menu),
Target::Window(window),
);
} else {
const MSG: &str = "MenuDesc<T> - T must match the application state.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("DelegateCtx::set_menu: {}", MSG)
}
}
}
}

/// A type that provides hooks for handling and modifying top-level events.
Expand Down
6 changes: 3 additions & 3 deletions druid/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub mod sys {
pub const HIDE_OTHERS: Selector = Selector::new("druid-builtin.menu-hide-others");

/// The selector for a command to create a new window.
pub const NEW_WINDOW: Selector = Selector::new("druid-builtin.new-window");
pub(crate) const NEW_WINDOW: Selector = Selector::new("druid-builtin.new-window");

/// The selector for a command to close a window.
///
Expand All @@ -139,13 +139,13 @@ pub mod sys {
/// object to be displayed.
///
/// [`ContextMenu`]: ../struct.ContextMenu.html
pub const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu");
pub(crate) const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu");

/// The selector for a command to set the window's menu. The argument should
/// be a [`MenuDesc`] object.
///
/// [`MenuDesc`]: ../struct.MenuDesc.html
pub const SET_MENU: Selector = Selector::new("druid-builtin.set-menu");
pub(crate) const SET_MENU: Selector = Selector::new("druid-builtin.set-menu");

/// Show the application preferences.
pub const SHOW_PREFERENCES: Selector = Selector::new("druid-builtin.menu-show-preferences");
Expand Down
90 changes: 86 additions & 4 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,28 @@

//! The context types that are passed into various widget methods.
use std::ops::{Deref, DerefMut};
use std::time::Duration;
use std::{
any::{Any, TypeId},
ops::{Deref, DerefMut},
time::Duration,
};

use crate::core::{BaseState, CommandQueue, FocusChange};
use crate::piet::Piet;
use crate::piet::RenderContext;
use crate::{
Affine, Command, Cursor, Insets, Point, Rect, Size, Target, Text, TimerToken, Vec2, WidgetId,
WindowHandle, WindowId,
commands, Affine, Command, ContextMenu, Cursor, Insets, MenuDesc, Point, Rect, Size, Target,
Text, TimerToken, Vec2, WidgetId, WindowDesc, WindowHandle, WindowId,
};

/// These allow type checking new windows and menus at runtime
#[derive(Clone, Copy)]
pub(crate) struct StateTypes {
pub window_desc: TypeId,
pub menu_desc: TypeId,
pub context_menu: TypeId,
}

/// A mutable context provided to event handling methods of widgets.
///
/// Widgets should call [`request_paint`] whenever an event causes a change
Expand All @@ -43,6 +54,7 @@ pub struct EventCtx<'a> {
pub(crate) focus_widget: Option<WidgetId>,
pub(crate) is_handled: bool,
pub(crate) is_root: bool,
pub(crate) state_types: StateTypes,
}

/// A mutable context provided to the [`lifecycle`] method on widgets.
Expand Down Expand Up @@ -127,6 +139,16 @@ pub struct PaintCtx<'a, 'b: 'a> {
#[derive(Debug, Clone)]
pub struct Region(Rect);

impl StateTypes {
pub fn new<T: Any>() -> Self {
StateTypes {
window_desc: TypeId::of::<WindowDesc<T>>(),
menu_desc: TypeId::of::<MenuDesc<T>>(),
context_menu: TypeId::of::<ContextMenu<T>>(),
}
}
}

impl<'a> EventCtx<'a> {
#[deprecated(since = "0.5.0", note = "use request_paint instead")]
pub fn invalidate(&mut self) {
Expand Down Expand Up @@ -242,6 +264,66 @@ impl<'a> EventCtx<'a> {
&self.window
}

/// Create a new window.
/// `T` must be the application's root `Data` type (the type provided to [`AppLauncher::launch`]).
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>) {
if self.state_types.window_desc == TypeId::of::<WindowDesc<T>>() {
self.submit_command(
Command::one_shot(commands::NEW_WINDOW, desc),
Target::Global,
);
} else {
const MSG: &str = "WindowDesc<T> - T must match the application state.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::new_window: {}", MSG)
}
}
}

/// Set the menu of the window containing the current widget.
/// `T` must be the application's root `Data` type (the type provided to [`AppLauncher::launch`]).
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
if self.state_types.menu_desc == TypeId::of::<MenuDesc<T>>() {
self.submit_command(
Command::new(commands::SET_MENU, menu),
Target::Window(self.window_id),
);
} else {
const MSG: &str = "MenuDesc<T> - T must match the application state.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::set_menu: {}", MSG)
}
}
}

/// Show the context menu in the window containing the current widget.
/// `T` must be the application's root `Data` type (the type provided to [`AppLauncher::launch`]).
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn show_context_menu<T: Any>(&mut self, menu: ContextMenu<T>) {
if self.state_types.context_menu == TypeId::of::<ContextMenu<T>>() {
self.submit_command(
Command::new(commands::SHOW_CONTEXT_MENU, menu),
Target::Window(self.window_id),
);
} else {
const MSG: &str = "ContextMenu<T> - T must match the application state.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::show_context_menu: {}", MSG)
}
}
}

/// Set the event as "handled", which stops its propagation to other
/// widgets.
pub fn set_handled(&mut self) {
Expand Down
1 change: 1 addition & 0 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
is_handled: false,
is_root: false,
focus_widget: ctx.focus_widget,
state_types: ctx.state_types,
};

let rect = child_ctx.base_state.layout_rect.unwrap_or_default();
Expand Down
7 changes: 5 additions & 2 deletions druid/src/win_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::{
WindowId,
};

use crate::command::sys as sys_cmd;
use crate::{command::sys as sys_cmd, contexts::StateTypes};

pub(crate) const RUN_COMMANDS_TOKEN: IdleToken = IdleToken::new(1);

Expand Down Expand Up @@ -187,7 +187,10 @@ impl<T: Data> Inner<T> {
ref env,
..
} = self;
let mut ctx = DelegateCtx { command_queue };
let mut ctx = DelegateCtx {
command_queue,
state_types: StateTypes::new::<T>(),
};
if let Some(delegate) = delegate {
Some(f(delegate, data, env, &mut ctx))
} else {
Expand Down
Loading

0 comments on commit 0acb07b

Please sign in to comment.