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

Common context state #970

Merged
merged 2 commits into from
May 22, 2020
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 @@ -51,6 +51,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
- `im::Vector` support for the `List` widget. ([#940] by [@xStrom])
- `LifeCycle::Size` event to inform widgets that their size changed. ([#953] by [@xStrom])
- `Button::dynamic` constructor. ([#963] by [@totsteps])
- `set_menu` method on `UpdateCtx` and `LifeCycleCtx` ([#970] by [@cmyr])

### Changed

Expand Down Expand Up @@ -219,6 +220,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
[#963]: https://github.com/xi-editor/druid/pull/963
[#967]: https://github.com/xi-editor/druid/pull/967
[#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
190 changes: 105 additions & 85 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,30 @@ use crate::{
Target, Text, TimerToken, Vec2, WidgetId, WindowDesc, WindowHandle, WindowId,
};

/// Static state that is shared between most contexts.
pub(crate) struct ContextState<'a> {
pub(crate) command_queue: &'a mut CommandQueue,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) root_app_data_type: TypeId,
}

/// A mutable context provided to event handling methods of widgets.
///
/// Widgets should call [`request_paint`] whenever an event causes a change
/// 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) state: &'a mut ContextState<'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 +63,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) state: &'a mut ContextState<'b>,
}

/// A mutable context provided to data update methods of widgets.
Expand All @@ -71,14 +74,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) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
}

Expand All @@ -87,12 +84,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, 'c: 'a> {
pub(crate) state: &'a mut ContextState<'b>,
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) text_factory: &'a mut Text<'c>,
pub(crate) mouse_pos: Option<Point>,
}

Expand All @@ -110,7 +105,7 @@ pub(crate) struct ZOrderPaintOp {
/// This struct is expected to grow, for example to include the
/// "damage region" indicating that only a subset of the entire
/// widget hierarchy needs repainting.
pub struct PaintCtx<'a, 'b: 'a> {
pub struct PaintCtx<'a, 'b> {
/// The render context for actually painting.
pub render_ctx: &'a mut Piet<'b>,
pub window_id: WindowId,
Expand All @@ -133,7 +128,7 @@ pub struct PaintCtx<'a, 'b: 'a> {
#[derive(Debug, Clone)]
pub struct Region(Rect);

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

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

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

/// Returns a reference to the current `WindowHandle`.
pub fn window(&self) -> &WindowHandle {
&self.window
&self.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.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 +268,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.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.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.state.window_id),
);
} else {
const MSG: &str = "ContextMenu<T> - T must match the application data type.";
Expand Down Expand Up @@ -431,10 +414,7 @@ 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.state.request_timer(&mut self.widget_state, deadline)
}

/// The layout size.
Expand All @@ -458,18 +438,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.state.submit_command(cmd.into(), target.into())
}

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

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

impl<'a> LifeCycleCtx<'a> {
impl LifeCycleCtx<'_, '_> {
#[deprecated(since = "0.5.0", note = "use request_paint instead")]
pub fn invalidate(&mut self) {
self.request_paint();
Expand Down Expand Up @@ -559,10 +534,7 @@ 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.state.request_timer(&mut self.widget_state, deadline)
}

/// The layout size.
Expand All @@ -586,17 +558,21 @@ 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.state.submit_command(cmd.into(), target.into())
}

/// 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>) {
self.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 @@ -650,10 +626,7 @@ 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.state.request_timer(&mut self.widget_state, deadline)
}

/// The layout size.
Expand All @@ -677,29 +650,33 @@ 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.state.submit_command(cmd.into(), target.into());
}

/// 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>) {
self.state.set_menu(menu)
}

/// Get an object which can create text layouts.
pub fn text(&mut self) -> Text {
self.window.text()
self.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.state.window
}

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

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

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

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

/// Set explicit paint [`Insets`] for this widget.
Expand All @@ -735,7 +712,7 @@ impl<'a, 'b> LayoutCtx<'a, 'b> {
}
}

impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
impl PaintCtx<'_, '_> {
/// get the `WidgetId` of the current widget.
pub fn widget_id(&self) -> WidgetId {
self.widget_state.id
Expand Down Expand Up @@ -883,6 +860,49 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
}
}

impl<'a> ContextState<'a> {
pub(crate) fn new<T: 'static>(
command_queue: &'a mut CommandQueue,
window: &'a WindowHandle,
window_id: WindowId,
) -> Self {
ContextState {
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