Skip to content

Commit

Permalink
Adds a hook for intercepting close requests. (linebender#1118)
Browse files Browse the repository at this point in the history
In druid-shell, a close request from the system will result in a call
to `WinHandler::request_close` instead of just immediately destroying
the window.

In druid, CLOSE_WINDOW commands are passed to the AppDelegate and down
the widget tree. Only if they are unhandled does the window get closed.
  • Loading branch information
jneem authored Aug 17, 2020
1 parent 90d81c9 commit 7b6f83e
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 12 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ You can find its changes [documented below](#060---2020-06-01).
- Re-export `druid_shell::Scalable` under `druid` namespace. ([#1075] by [@ForLoveOfCats])
- `TextBox` now supports ctrl and shift hotkeys. ([#1076] by [@vkahl])
- Added selection text color to textbox. ([#1093] by [@sysint64])
- Close requests from the shell can now be intercepted ([#1118] by [@jneem])

### Changed

Expand Down Expand Up @@ -45,7 +46,7 @@ You can find its changes [documented below](#060---2020-06-01).
- `ViewSwitcher` now skips the update after switching widgets. ([#1113] by [@finnerale])
- Key and KeyOrValue derive Clone ([#1119] by [@rjwittams])
- Allow submit_command from the layout method in Widgets ([#1119] by [@rjwittams])
- Allow derivation of lenses for generic types ([#1120]) by [@rjwittams])
- Allow derivation of lenses for generic types ([#1120]) by [@rjwittams])

### Visual

Expand Down Expand Up @@ -382,6 +383,7 @@ Last release without a changelog :(
[#1093]: https://github.com/linebender/druid/pull/1093
[#1100]: https://github.com/linebender/druid/pull/1100
[#1103]: https://github.com/linebender/druid/pull/1103
[#1118]: https://github.com/linebender/druid/pull/1118
[#1119]: https://github.com/linebender/druid/pull/1119
[#1120]: https://github.com/linebender/druid/pull/1120

Expand Down
4 changes: 4 additions & 0 deletions druid-shell/examples/invalidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ impl WinHandler for InvalidateTest {
}
}

fn request_close(&mut self) {
self.handle.close();
}

fn destroy(&mut self) {
Application::global().quit()
}
Expand Down
4 changes: 4 additions & 0 deletions druid-shell/examples/perftest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ impl WinHandler for PerfTest {
self.size = size;
}

fn request_close(&mut self) {
self.handle.close();
}

fn destroy(&mut self) {
Application::global().quit()
}
Expand Down
89 changes: 89 additions & 0 deletions druid-shell/examples/quit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2020 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.

use std::any::Any;

use druid_shell::kurbo::{Line, Rect, Size};
use druid_shell::piet::{Color, RenderContext};

use druid_shell::{Application, HotKey, Menu, SysMods, WinHandler, WindowBuilder, WindowHandle};

const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22);
const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea);

#[derive(Default)]
struct QuitState {
quit_count: u32,
size: Size,
handle: WindowHandle,
}

impl WinHandler for QuitState {
fn connect(&mut self, handle: &WindowHandle) {
self.handle = handle.clone();
}

fn paint(&mut self, piet: &mut piet_common::Piet, _: Rect) -> bool {
let rect = self.size.to_rect();
piet.fill(rect, &BG_COLOR);
piet.stroke(Line::new((10.0, 50.0), (90.0, 90.0)), &FG_COLOR, 1.0);
false
}

fn size(&mut self, size: Size) {
self.size = size;
}

fn request_close(&mut self) {
self.quit_count += 1;
if self.quit_count >= 5 {
self.handle.close();
} else {
log::info!("Don't wanna quit");
}
}

fn destroy(&mut self) {
Application::global().quit()
}

fn as_any(&mut self) -> &mut dyn Any {
self
}
}

fn main() {
simple_logger::init().expect("Failed to init simple logger");
let app = Application::new().unwrap();
let mut file_menu = Menu::new();
file_menu.add_item(
0x100,
"E&xit",
Some(&HotKey::new(SysMods::Cmd, "q")),
true,
false,
);
let mut menubar = Menu::new();
menubar.add_dropdown(Menu::new(), "Application", true);

let mut builder = WindowBuilder::new(app.clone());
builder.set_handler(Box::new(QuitState::default()));
builder.set_title("Quit example");
builder.set_menu(menubar);

let window = builder.build().unwrap();
window.show();

app.run(None);
}
4 changes: 4 additions & 0 deletions druid-shell/examples/shello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ impl WinHandler for HelloState {
self.size = size;
}

fn request_close(&mut self) {
self.handle.close();
}

fn destroy(&mut self) {
Application::global().quit()
}
Expand Down
20 changes: 18 additions & 2 deletions druid-shell/src/platform/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ use crate::piet::{Piet, RenderContext};
use crate::common_util::IdleCallback;
use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo};
use crate::error::Error as ShellError;
use crate::keyboard::{KbKey, KeyState, KeyEvent, Modifiers};
use crate::keyboard::{KbKey, KeyEvent, KeyState, Modifiers};
use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent};
use crate::scale::{Scale, Scalable, ScaledArea};
use crate::scale::{Scalable, Scale, ScaledArea};
use crate::window::{IdleToken, Text, TimerToken, WinHandler};

use super::application::Application;
Expand Down Expand Up @@ -116,6 +116,9 @@ pub(crate) struct WindowState {
window: ApplicationWindow,
scale: Cell<Scale>,
area: Cell<ScaledArea>,
/// Used to determine whether to honor close requests from the system: we inhibit them unless
/// this is true, and this gets set to true when our client requests a close.
closing: Cell<bool>,
drawing_area: DrawingArea,
pub(crate) handler: RefCell<Box<dyn WinHandler>>,
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
Expand Down Expand Up @@ -198,6 +201,7 @@ impl WindowBuilder {
window,
scale: Cell::new(scale),
area: Cell::new(area),
closing: Cell::new(false),
drawing_area,
handler: RefCell::new(handler),
idle_queue: Arc::new(Mutex::new(vec![])),
Expand Down Expand Up @@ -509,6 +513,17 @@ impl WindowBuilder {
Inhibit(true)
}));

win_state
.window
.connect_delete_event(clone!(handle => move |_widget, _ev| {
if let Some(state) = handle.state.upgrade() {
state.handler.borrow_mut().request_close();
Inhibit(!state.closing.get())
} else {
Inhibit(false)
}
}));

win_state
.drawing_area
.connect_destroy(clone!(handle => move |_widget| {
Expand Down Expand Up @@ -556,6 +571,7 @@ impl WindowHandle {
/// Close the window.
pub fn close(&self) {
if let Some(state) = self.state.upgrade() {
state.closing.set(true);
state.window.close();
}
}
Expand Down
10 changes: 10 additions & 0 deletions druid-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,16 @@ pub trait WinHandler {
#[allow(unused_variables)]
fn got_focus(&mut self) {}

/// Called when the shell requests to close the window, for example because the user clicked
/// the little "X" in the titlebar.
///
/// If you want to actually close the window in response to this request, call
/// [`WindowHandle::close`]. If you don't implement this method, clicking the titlebar "X" will
/// have no effect.
///
/// [`WindowHandle::close`]: struct.WindowHandle.html#tymethod.close
fn request_close(&mut self) {}

/// Called when the window is being destroyed. Note that this happens
/// earlier in the sequence than drop (at WM_DESTROY, while the latter is
/// WM_NCDESTROY).
Expand Down
34 changes: 25 additions & 9 deletions druid/src/win_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,23 +306,26 @@ impl<T: Data> Inner<T> {
}
}

fn dispatch_cmd(&mut self, target: Target, cmd: Command) {
/// Returns `true` if the command was handled.
fn dispatch_cmd(&mut self, target: Target, cmd: Command) -> bool {
if !self.delegate_cmd(target, &cmd) {
return;
return true;
}

match target {
Target::Window(id) => {
// first handle special window-level events
if cmd.is(sys_cmd::SET_MENU) {
return self.set_menu(id, &cmd);
self.set_menu(id, &cmd);
return true;
}
if cmd.is(sys_cmd::SHOW_CONTEXT_MENU) {
return self.show_context_menu(id, &cmd);
self.show_context_menu(id, &cmd);
return true;
}
if let Some(w) = self.windows.get_mut(id) {
let event = Event::Command(cmd);
w.event(&mut self.command_queue, event, &mut self.data, &self.env);
return w.event(&mut self.command_queue, event, &mut self.data, &self.env);
}
}
// in this case we send it to every window that might contain
Expand All @@ -332,19 +335,20 @@ impl<T: Data> Inner<T> {
let event =
Event::Internal(InternalEvent::TargetedCommand(id.into(), cmd.clone()));
if w.event(&mut self.command_queue, event, &mut self.data, &self.env) {
break;
return true;
}
}
}
Target::Global => {
for w in self.windows.iter_mut() {
let event = Event::Command(cmd.clone());
if w.event(&mut self.command_queue, event, &mut self.data, &self.env) {
break;
return true;
}
}
}
}
false
}

fn do_window_event(&mut self, source_id: WindowId, event: Event) -> bool {
Expand Down Expand Up @@ -552,7 +556,11 @@ impl<T: Data> AppState<T> {
// FIXME: we need to be able to open a file without a window handle
T::Window(id) if cmd.is(sys_cmd::SHOW_OPEN_PANEL) => self.show_open_panel(cmd, id),
T::Window(id) if cmd.is(sys_cmd::SHOW_SAVE_PANEL) => self.show_save_panel(cmd, id),
T::Window(id) if cmd.is(sys_cmd::CLOSE_WINDOW) => self.request_close_window(id),
T::Window(id) if cmd.is(sys_cmd::CLOSE_WINDOW) => {
if !self.inner.borrow_mut().dispatch_cmd(target, cmd) {
self.request_close_window(id);
}
}
T::Window(id) if cmd.is(sys_cmd::SHOW_WINDOW) => self.show_window(id),
T::Window(id) if cmd.is(sys_cmd::PASTE) => self.do_paste(id),
_ if cmd.is(sys_cmd::CLOSE_WINDOW) => {
Expand All @@ -561,7 +569,9 @@ impl<T: Data> AppState<T> {
_ if cmd.is(sys_cmd::SHOW_WINDOW) => {
log::warn!("SHOW_WINDOW command must target a window.")
}
_ => self.inner.borrow_mut().dispatch_cmd(target, cmd),
_ => {
self.inner.borrow_mut().dispatch_cmd(target, cmd);
}
}
}

Expand Down Expand Up @@ -737,6 +747,12 @@ impl<T: Data> WinHandler for DruidHandler<T> {
self
}

fn request_close(&mut self) {
self.app_state
.handle_cmd(self.window_id.into(), sys_cmd::CLOSE_WINDOW.into());
self.app_state.inner.borrow_mut().do_update();
}

fn destroy(&mut self) {
self.app_state.remove_window(self.window_id);
}
Expand Down

0 comments on commit 7b6f83e

Please sign in to comment.