Skip to content

Commit

Permalink
Refactor contexts for more code reuse
Browse files Browse the repository at this point in the history
This adds a new type, RootState that holds onto all of the
app/window-scoped state that is shared between all non-paint
contexts.
  • Loading branch information
cmyr committed May 21, 2020
1 parent 1962294 commit f1d1650
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 171 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
- Replaced `NEW_WINDOW`, `SET_MENU` and `SHOW_CONTEXT_MENU` commands with methods on `EventCtx` and `DelegateCtx`. ([#931] by [@finnerale])
- Replaced `Command::one_shot` and `::take_object` with a `SingleUse` payload wrapper type. ([#959] by [@finnerale])
- Renamed `BaseState` to `WidgetState` ([#969] by [@cmyr])
- Add `set_menu` method to `UpdateCtx` and `LifeCycleCtx` ([#970] by [@cmyr])

### Deprecated

Expand Down Expand Up @@ -217,6 +218,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
[#961]: https://github.com/xi-editor/druid/pull/961
[#963]: https://github.com/xi-editor/druid/pull/963
[#969]: https://github.com/xi-editor/druid/pull/969
[#970]: https://github.com/xi-editor/druid/pull/970

## [0.5.0] - 2020-04-01

Expand Down
189 changes: 81 additions & 108 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::{
time::Duration,
};

use crate::core::{CommandQueue, FocusChange, WidgetState};
use crate::core::{CommandQueue, FocusChange, RootState, WidgetState};
use crate::piet::Piet;
use crate::piet::RenderContext;
use crate::{
Expand All @@ -34,19 +34,16 @@ use crate::{
/// in the widget's appearance, to schedule a repaint.
///
/// [`request_paint`]: #method.request_paint
pub struct EventCtx<'a> {
pub struct EventCtx<'a, 'b> {
// Note: there's a bunch of state that's just passed down, might
// want to group that into a single struct.
pub(crate) root_state: &'a mut RootState<'b>,
pub(crate) cursor: &'a mut Option<Cursor>,
/// Commands submitted to be run after this event.
pub(crate) command_queue: &'a mut CommandQueue,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) focus_widget: Option<WidgetId>,
pub(crate) is_handled: bool,
pub(crate) is_root: bool,
pub(crate) app_data_type: TypeId,
}

/// A mutable context provided to the [`lifecycle`] method on widgets.
Expand All @@ -58,11 +55,9 @@ pub struct EventCtx<'a> {
/// [`lifecycle`]: trait.Widget.html#tymethod.lifecycle
/// [`register_child`]: #method.register_child
/// [`LifeCycle::WidgetAdded`]: enum.LifeCycle.html#variant.WidgetAdded
pub struct LifeCycleCtx<'a> {
pub(crate) command_queue: &'a mut CommandQueue,
pub struct LifeCycleCtx<'a, 'b> {
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) root_state: &'a mut RootState<'b>,
}

/// A mutable context provided to data update methods of widgets.
Expand All @@ -71,14 +66,8 @@ pub struct LifeCycleCtx<'a> {
/// in the widget's appearance, to schedule a repaint.
///
/// [`request_paint`]: #method.request_paint
pub struct UpdateCtx<'a> {
pub(crate) window: &'a WindowHandle,
// Discussion: we probably want to propagate more fine-grained
// invalidations, which would mean a structure very much like
// `EventCtx` (and possibly using the same structure). But for
// now keep it super-simple.
pub(crate) command_queue: &'a mut CommandQueue,
pub(crate) window_id: WindowId,
pub struct UpdateCtx<'a, 'b> {
pub(crate) root_state: &'a mut RootState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
}

Expand All @@ -87,12 +76,10 @@ pub struct UpdateCtx<'a> {
/// As of now, the main service provided is access to a factory for
/// creating text layout objects, which are likely to be useful
/// during widget layout.
pub struct LayoutCtx<'a, 'b: 'a> {
pub(crate) command_queue: &'a mut CommandQueue,
pub struct LayoutCtx<'a, 'b: 'a, 'c> {
pub(crate) root_state: &'a mut RootState<'c>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) text_factory: &'a mut Text<'b>,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) mouse_pos: Option<Point>,
}

Expand Down Expand Up @@ -133,7 +120,7 @@ pub struct PaintCtx<'a, 'b: 'a> {
#[derive(Debug, Clone)]
pub struct Region(Rect);

impl<'a> EventCtx<'a> {
impl<'a, 'b> EventCtx<'a, 'b> {
#[deprecated(since = "0.5.0", note = "use request_paint instead")]
pub fn invalidate(&mut self) {
self.request_paint();
Expand Down Expand Up @@ -183,7 +170,7 @@ impl<'a> EventCtx<'a> {

/// Get an object which can create text layouts.
pub fn text(&mut self) -> Text {
self.window.text()
self.root_state.window.text()
}

/// Set the cursor icon.
Expand Down Expand Up @@ -245,15 +232,15 @@ impl<'a> EventCtx<'a> {

/// Returns a reference to the current `WindowHandle`.
pub fn window(&self) -> &WindowHandle {
&self.window
&self.root_state.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.app_data_type == TypeId::of::<T>() {
if self.root_state.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
Command::new(commands::NEW_WINDOW, SingleUse::new(desc)),
Target::Global,
Expand All @@ -273,30 +260,18 @@ impl<'a> EventCtx<'a> {
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
if self.app_data_type == TypeId::of::<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 data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::set_menu: {}", MSG)
}
}
self.root_state.set_menu(menu);
}

/// 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.app_data_type == TypeId::of::<T>() {
if self.root_state.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
Command::new(commands::SHOW_CONTEXT_MENU, menu),
Target::Window(self.window_id),
Target::Window(self.root_state.window_id),
);
} else {
const MSG: &str = "ContextMenu<T> - T must match the application data type.";
Expand Down Expand Up @@ -431,10 +406,8 @@ impl<'a> EventCtx<'a> {
/// The return value is a token, which can be used to associate the
/// request with the event.
pub fn request_timer(&mut self, deadline: Duration) -> TimerToken {
self.widget_state.request_timer = true;
let timer_token = self.window.request_timer(deadline);
self.widget_state.add_timer(timer_token);
timer_token
self.root_state
.request_timer(&mut self.widget_state, deadline)
}

/// The layout size.
Expand All @@ -458,18 +431,13 @@ impl<'a> EventCtx<'a> {
///
/// [`Command`]: struct.Command.html
/// [`update`]: trait.Widget.html#tymethod.update
pub fn submit_command(
&mut self,
command: impl Into<Command>,
target: impl Into<Option<Target>>,
) {
let target = target.into().unwrap_or_else(|| self.window_id.into());
self.command_queue.push_back((target, command.into()))
pub fn submit_command(&mut self, cmd: impl Into<Command>, target: impl Into<Option<Target>>) {
self.root_state.submit_command(cmd.into(), target.into())
}

/// Get the window id.
pub fn window_id(&self) -> WindowId {
self.window_id
self.root_state.window_id
}

/// get the `WidgetId` of the current widget.
Expand All @@ -478,7 +446,7 @@ impl<'a> EventCtx<'a> {
}
}

impl<'a> LifeCycleCtx<'a> {
impl<'a, 'b> LifeCycleCtx<'a, 'b> {
#[deprecated(since = "0.5.0", note = "use request_paint instead")]
pub fn invalidate(&mut self) {
self.request_paint();
Expand Down Expand Up @@ -559,10 +527,8 @@ impl<'a> LifeCycleCtx<'a> {
/// The return value is a token, which can be used to associate the
/// request with the event.
pub fn request_timer(&mut self, deadline: Duration) -> TimerToken {
self.widget_state.request_timer = true;
let timer_token = self.window.request_timer(deadline);
self.widget_state.add_timer(timer_token);
timer_token
self.root_state
.request_timer(&mut self.widget_state, deadline)
}

/// The layout size.
Expand All @@ -586,13 +552,8 @@ impl<'a> LifeCycleCtx<'a> {
///
/// [`Command`]: struct.Command.html
/// [`update`]: trait.Widget.html#tymethod.update
pub fn submit_command(
&mut self,
command: impl Into<Command>,
target: impl Into<Option<Target>>,
) {
let target = target.into().unwrap_or_else(|| self.window_id.into());
self.command_queue.push_back((target, command.into()))
pub fn submit_command(&mut self, cmd: impl Into<Command>, target: impl Into<Option<Target>>) {
self.root_state.submit_command(cmd.into(), target.into())
}

/// Set the menu of the window containing the current widget.
Expand All @@ -601,23 +562,11 @@ impl<'a> LifeCycleCtx<'a> {
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
if self.app_data_type == TypeId::of::<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 data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::set_menu: {}", MSG)
}
}
self.root_state.set_menu(menu);
}
}

impl<'a> UpdateCtx<'a> {
impl<'a, 'b> UpdateCtx<'a, 'b> {
#[deprecated(since = "0.5.0", note = "use request_paint instead")]
pub fn invalidate(&mut self) {
self.request_paint();
Expand Down Expand Up @@ -671,10 +620,8 @@ impl<'a> UpdateCtx<'a> {
/// The return value is a token, which can be used to associate the
/// request with the event.
pub fn request_timer(&mut self, deadline: Duration) -> TimerToken {
self.widget_state.request_timer = true;
let timer_token = self.window.request_timer(deadline);
self.widget_state.add_timer(timer_token);
timer_token
self.root_state
.request_timer(&mut self.widget_state, deadline)
}

/// The layout size.
Expand All @@ -698,13 +645,8 @@ impl<'a> UpdateCtx<'a> {
/// layout, and paint have completed; this will trigger a new event cycle.
///
/// [`Command`]: struct.Command.html
pub fn submit_command(
&mut self,
command: impl Into<Command>,
target: impl Into<Option<Target>>,
) {
let target = target.into().unwrap_or_else(|| self.window_id.into());
self.command_queue.push_back((target, command.into()))
pub fn submit_command(&mut self, cmd: impl Into<Command>, target: impl Into<Option<Target>>) {
self.root_state.submit_command(cmd.into(), target.into());
}

/// Set the menu of the window containing the current widget.
Expand All @@ -713,35 +655,23 @@ impl<'a> UpdateCtx<'a> {
///
/// [`AppLauncher::launch`]: struct.AppLauncher.html#method.launch
pub fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
if self.app_data_type == TypeId::of::<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 data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::set_menu: {}", MSG)
}
}
self.root_state.set_menu(menu)
}

/// Get an object which can create text layouts.
pub fn text(&mut self) -> Text {
self.window.text()
self.root_state.window.text()
}

/// Returns a reference to the current `WindowHandle`.
//TODO: can we delete this? where is it used?
pub fn window(&self) -> &WindowHandle {
&self.window
&self.root_state.window
}

/// Get the window id.
pub fn window_id(&self) -> WindowId {
self.window_id
self.root_state.window_id
}

/// get the `WidgetId` of the current widget.
Expand All @@ -750,15 +680,15 @@ impl<'a> UpdateCtx<'a> {
}
}

impl<'a, 'b> LayoutCtx<'a, 'b> {
impl<'a, 'b, 'c> LayoutCtx<'a, 'b, 'c> {
/// Get an object which can create text layouts.
pub fn text(&mut self) -> &mut Text<'b> {
&mut self.text_factory
}

/// Get the window id.
pub fn window_id(&self) -> WindowId {
self.window_id
self.root_state.window_id
}

/// Set explicit paint [`Insets`] for this widget.
Expand Down Expand Up @@ -925,6 +855,49 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
}
}

impl<'a> RootState<'a> {
pub(crate) fn new<T: 'static>(
command_queue: &'a mut CommandQueue,
window: &'a WindowHandle,
window_id: WindowId,
) -> Self {
RootState {
command_queue,
window,
window_id,
root_app_data_type: TypeId::of::<T>(),
}
}

fn submit_command(&mut self, command: Command, target: Option<Target>) {
let target = target.unwrap_or_else(|| self.window_id.into());
self.command_queue.push_back((target, command))
}

fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
if self.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
Command::new(commands::SET_MENU, menu),
Some(Target::Window(self.window_id)),
);
} else {
const MSG: &str = "MenuDesc<T> - T must match the application data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::set_menu: {}", MSG)
}
}
}

fn request_timer(&self, widget_state: &mut WidgetState, deadline: Duration) -> TimerToken {
widget_state.request_timer = true;
let timer_token = self.window.request_timer(deadline);
widget_state.add_timer(timer_token);
timer_token
}
}

impl Region {
/// An empty region.
pub const EMPTY: Region = Region(Rect::ZERO);
Expand Down
Loading

0 comments on commit f1d1650

Please sign in to comment.