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

make most window types have a child window #1919

Merged
merged 15 commits into from Sep 18, 2021
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -76,6 +76,8 @@ You can find its changes [documented below](#070---2021-01-01).
- Move macOS only function to Mac extension trait ([#1863] by [@Maan2003])
- x11: Only query atoms once instead of per window ([#1865] by [@psychon])
- remove prefix from platform extension traits ([#1873] by [@Maan2003])
- Remove `set_level` on windows ([#1919] by [@JAicewizard])
- Add parent windows to non-main windows. (Coordinate space is now from their origin) ([#1919] by [@JAicewizard])
- `ListIter` implementations for `Arc<Vec<T>>`, `(S, Arc<Vec<T>>)`, `Arc<VecDequeue<T>>` and `(S, Arc<VecDequeue<T>>)` ([#1967] by [@xarvic])

### Deprecated
Expand Down Expand Up @@ -786,6 +788,7 @@ Last release without a changelog :(
[#1885]: https://github.com/linebender/druid/pull/1885
[#1886]: https://github.com/linebender/druid/pull/1886
[#1907]: https://github.com/linebender/druid/pull/1907
[#1919]: https://github.com/linebender/druid/pull/1919
[#1929]: https://github.com/linebender/druid/pull/1929
[#1947]: https://github.com/linebender/druid/pull/1947
[#1967]: https://github.com/linebender/druid/pull/1967
Expand Down
129 changes: 85 additions & 44 deletions druid-shell/src/backend/gtk/window.rs
Expand Up @@ -103,13 +103,24 @@ macro_rules! clone {
);
}

#[derive(Clone, Default)]
#[derive(Clone, Default, Debug)]
pub struct WindowHandle {
pub(crate) state: Weak<WindowState>,
// Ensure that we don't implement Send, because it isn't actually safe to send the WindowState.
marker: std::marker::PhantomData<*const ()>,
}

impl PartialEq for WindowHandle {
fn eq(&self, other: &Self) -> bool {
match (self.state.upgrade(), other.state.upgrade()) {
(None, None) => true,
(Some(s), Some(o)) => std::sync::Arc::ptr_eq(&s, &o),
(_, _) => false,
}
}
}
impl Eq for WindowHandle {}

#[cfg(feature = "raw-win-handle")]
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
Expand Down Expand Up @@ -191,6 +202,17 @@ pub(crate) struct WindowState {

request_animation: Cell<bool>,
in_draw: Cell<bool>,

parent: Option<crate::WindowHandle>,
}

impl std::fmt::Debug for WindowState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("WindowState{")?;
self.window.fmt(f)?;
f.write_str("}")?;
Ok(())
}
}

#[derive(Clone, PartialEq)]
Expand Down Expand Up @@ -293,7 +315,48 @@ impl WindowBuilder {
window.add(&vbox);
let drawing_area = gtk::DrawingArea::new();

let win_state = Arc::new(WindowState {
// Set the parent widget and handle level specific code
let mut parent: Option<crate::WindowHandle> = None;
if let Some(level) = &self.level {
let hint = match level {
WindowLevel::AppWindow => WindowTypeHint::Normal,
WindowLevel::Tooltip(_) => WindowTypeHint::Tooltip,
WindowLevel::DropDown(_) => WindowTypeHint::DropdownMenu,
WindowLevel::Modal(_) => WindowTypeHint::Dialog,
};

window.set_type_hint(hint);

match &level {
WindowLevel::Tooltip(p) => {
parent = Some(p.clone());
}
WindowLevel::DropDown(p) => {
parent = Some(p.clone());
}
WindowLevel::Modal(p) => {
parent = Some(p.clone());
window.set_urgency_hint(true);
window.set_modal(true);
}
_ => (),
};
if let Some(parent) = &parent {
if let Some(parent_state) = parent.0.state.upgrade() {
window.set_transient_for(Some(&parent_state.window));
}
}

let override_redirect = match level {
WindowLevel::AppWindow => false,
WindowLevel::Tooltip(_) | WindowLevel::DropDown(_) | WindowLevel::Modal(_) => true,
};
if let Some(window) = window.window() {
window.set_override_redirect(override_redirect);
}
}

let state = WindowState {
window,
scale: Cell::new(scale),
area: Cell::new(area),
Expand All @@ -311,7 +374,10 @@ impl WindowBuilder {
deferred_queue: RefCell::new(Vec::new()),
request_animation: Cell::new(false),
in_draw: Cell::new(false),
});
parent,
};

let win_state = Arc::new(state);

self.app
.gtk_app()
Expand All @@ -326,12 +392,10 @@ impl WindowBuilder {
state: Arc::downgrade(&win_state),
marker: std::marker::PhantomData,
};
if let Some(level) = self.level {
handle.set_level(level);
}
if let Some(pos) = self.position {
handle.set_position(pos);
}

if let Some(state) = self.state {
handle.set_window_state(state)
}
Expand Down Expand Up @@ -735,10 +799,6 @@ impl WindowBuilder {
.expect("realize didn't create window")
.set_event_compression(false);

if let Some(level) = self.level {
handle.set_override_redirect(level);
}

let size = self.size;
win_state.with_handler(|h| {
h.connect(&handle.clone().into());
Expand Down Expand Up @@ -905,7 +965,15 @@ impl WindowHandle {
}
}

pub fn set_position(&self, position: Point) {
pub fn set_position(&self, mut position: Point) {
// TODO: Make the window follow the parent.
if let Some(state) = self.state.upgrade() {
if let Some(parent_state) = &state.parent {
let pos = (*parent_state).get_position();
position += (pos.x, pos.y)
}
};

if let Some(state) = self.state.upgrade() {
let px = position.to_px(state.scale.get());
state.window.move_(px.x as i32, px.y as i32)
Expand All @@ -915,7 +983,12 @@ impl WindowHandle {
pub fn get_position(&self) -> Point {
if let Some(state) = self.state.upgrade() {
let (x, y) = state.window.position();
Point::new(x as f64, y as f64).to_dp(state.scale.get())
let mut position = Point::new(x as f64, y as f64);
if let Some(parent_state) = &state.parent {
let pos = (*parent_state).get_position();
position -= (pos.x, pos.y)
}
position.to_dp(state.scale.get())
} else {
Point::new(0.0, 0.0)
}
Expand Down Expand Up @@ -945,38 +1018,6 @@ impl WindowHandle {
}
}

pub fn set_level(&self, level: WindowLevel) {
if let Some(state) = self.state.upgrade() {
let hint = match level {
WindowLevel::AppWindow => WindowTypeHint::Normal,
WindowLevel::Tooltip => WindowTypeHint::Tooltip,
WindowLevel::DropDown => WindowTypeHint::DropdownMenu,
WindowLevel::Modal => WindowTypeHint::Dialog,
};

state.window.set_type_hint(hint);
}

self.set_override_redirect(level);
}

/// The override-redirect flag tells the window manager not to mess with the window; it should
/// be set for things like tooltips, dropdowns, etc.
///
/// Note that this is exposed on the GDK window, so we can't set it until the GTK window is
/// realized.
fn set_override_redirect(&self, level: WindowLevel) {
let override_redirect = match level {
WindowLevel::AppWindow => false,
WindowLevel::Tooltip | WindowLevel::DropDown | WindowLevel::Modal => true,
};
if let Some(state) = self.state.upgrade() {
if let Some(window) = state.window.window() {
window.set_override_redirect(override_redirect);
}
}
}

pub fn set_size(&self, size: Size) {
if let Some(state) = self.state.upgrade() {
let px = size.to_px(state.scale.get());
Expand Down
65 changes: 57 additions & 8 deletions druid-shell/src/backend/mac/window.rs
Expand Up @@ -89,9 +89,9 @@ mod levels {
use WindowLevel::*;
match window_level {
AppWindow => NSNormalWindowLevel,
Tooltip => NSFloatingWindowLevel,
DropDown => NSFloatingWindowLevel,
Modal => NSModalPanelWindowLevel,
Tooltip(_) => NSFloatingWindowLevel,
DropDown(_) => NSFloatingWindowLevel,
Modal(_) => NSModalPanelWindowLevel,
}
}
}
Expand All @@ -103,6 +103,24 @@ pub(crate) struct WindowHandle {
nsview: WeakPtr,
idle_queue: Weak<Mutex<Vec<IdleKind>>>,
}
impl PartialEq for WindowHandle {
fn eq(&self, other: &Self) -> bool {
match (self.idle_queue.upgrade(), other.idle_queue.upgrade()) {
(None, None) => true,
(Some(s), Some(o)) => std::sync::Arc::ptr_eq(&s, &o),
(_, _) => false,
}
}
}
impl Eq for WindowHandle {}

impl std::fmt::Debug for WindowHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("WindowHandle{\n")?;
f.write_str("}")?;
Ok(())
}
}

impl Default for WindowHandle {
fn default() -> Self {
Expand Down Expand Up @@ -159,6 +177,7 @@ struct ViewState {
keyboard_state: KeyboardState,
text: PietText,
active_text_input: Option<TextFieldToken>,
parent: Option<crate::WindowHandle>,
}

#[derive(Clone, PartialEq)]
Expand Down Expand Up @@ -290,7 +309,13 @@ impl WindowBuilder {
}

if let Some(level) = self.level {
handle.set_level(level)
match &level {
WindowLevel::Tooltip(parent) => (*view_state).parent = Some(parent.clone()),
WindowLevel::DropDown(parent) => (*view_state).parent = Some(parent.clone()),
WindowLevel::Modal(parent) => (*view_state).parent = Some(parent.clone()),
_ => {}
}
handle.set_level(level);
}

// set_window_state above could have invalidated the frame size
Expand Down Expand Up @@ -533,6 +558,7 @@ fn make_view(handler: Box<dyn WinHandler>) -> (id, Weak<Mutex<Vec<IdleKind>>>) {
keyboard_state,
text: PietText::new_with_unique_state(),
active_text_input: None,
parent: None,
};
let state_ptr = Box::into_raw(Box::new(state));
(*view).set_ivar("viewState", state_ptr as *mut c_void);
Expand Down Expand Up @@ -1198,7 +1224,21 @@ impl WindowHandle {
pub fn show_titlebar(&self, _show_titlebar: bool) {}

// Need to translate mac y coords, as they start from bottom left
pub fn set_position(&self, position: Point) {
pub fn set_position(&self, mut position: Point) {
// TODO: Maybe @cmyr can get this into a state where modal windows follow the parent?
// There is an API to do child windows, (https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow)
// but I have no good way of testing and making sure this works.
unsafe {
if let Some(view) = self.nsview.load().as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if let Some(parent_state) = &state.parent {
let pos = (*parent_state).get_position();
position += (pos.x, pos.y)
}
}
}

self.defer(DeferredOp::SetPosition(position))
}

Expand All @@ -1210,10 +1250,19 @@ impl WindowHandle {
let window: id = msg_send![*self.nsview.load(), window];
let current_frame: NSRect = msg_send![window, frame];

Point::new(
let mut position = Point::new(
current_frame.origin.x,
screen_height - current_frame.origin.y - current_frame.size.height,
)
);
if let Some(view) = self.nsview.load().as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if let Some(parent_state) = &state.parent {
let pos = (*parent_state).get_position();
position -= (pos.x, pos.y)
}
}
position
}
}

Expand Down Expand Up @@ -1245,7 +1294,7 @@ impl WindowHandle {
}
}

pub fn set_level(&self, level: WindowLevel) {
fn set_level(&self, level: WindowLevel) {
unsafe {
let level = levels::as_raw_window_level(level);
let window: id = msg_send![*self.nsview.load(), window];
Expand Down
14 changes: 10 additions & 4 deletions druid-shell/src/backend/web/window.rs
Expand Up @@ -79,6 +79,16 @@ pub(crate) struct WindowBuilder {

#[derive(Clone, Default)]
pub struct WindowHandle(Weak<WindowState>);
impl PartialEq for WindowHandle {
fn eq(&self, other: &Self) -> bool {
match (self.0.upgrade(), other.0.upgrade()) {
(None, None) => true,
(Some(s), Some(o)) => std::rc::Rc::ptr_eq(&s, &o),
(_, _) => false,
}
}
}
impl Eq for WindowHandle {}

#[cfg(feature = "raw-win-handle")]
unsafe impl HasRawWindowHandle for WindowHandle {
Expand Down Expand Up @@ -479,10 +489,6 @@ impl WindowHandle {
warn!("WindowHandle::set_position unimplemented for web");
}

pub fn set_level(&self, _level: WindowLevel) {
warn!("WindowHandle::set_level is currently unimplemented for web.");
}

pub fn get_position(&self) -> Point {
warn!("WindowHandle::get_position unimplemented for web.");
Point::new(0.0, 0.0)
Expand Down