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

Subwindow updates #1532

Merged
merged 8 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ You can find its changes [documented below](#070---2021-01-01).
- RichTextBuilder ([#1520] by [@Maan2003])
- `get_external_handle` on `DelegateCtx` ([#1526] by [@Maan2003])
- `AppLauncher::localization_resources` to use custom l10n resources. ([#1528] by [@edwin0cheng])
- Shell: get_content_insets and mac implementation ([#XXXX] by [@rjwittams])
- Contexts: to_window and to_screen (useful for relatively positioning sub windows) ([#XXXX] by [@rjwittams])
- WindowSizePolicy: allow windows to be sized by their content ([#XXXX] by [@rjwittams])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjwittams might want to pr something to fix these up?


### Changed

Expand Down
7 changes: 6 additions & 1 deletion druid-shell/src/platform/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use gio::ApplicationExt;
use gtk::prelude::*;
use gtk::{AccelGroup, ApplicationWindow, DrawingArea, SettingsExt};

use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::piet::{Piet, PietText, RenderContext};

use crate::common_util::{ClickCounter, IdleCallback};
Expand Down Expand Up @@ -818,6 +818,11 @@ impl WindowHandle {
}
}

pub fn get_content_insets(&self) -> Insets {
rjwittams marked this conversation as resolved.
Show resolved Hide resolved
log::warn!("WindowHandle::get_content_insets unimplemented for GTK platforms.");
Insets::ZERO
}

pub fn set_level(&self, level: WindowLevel) {
if let Some(state) = self.state.upgrade() {
let hint = match level {
Expand Down
156 changes: 104 additions & 52 deletions druid-shell/src/platform/mac/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use objc::rc::WeakPtr;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};

use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::piet::{Piet, PietText, RenderContext};

use super::appkit::{
Expand Down Expand Up @@ -127,10 +127,17 @@ pub(crate) struct IdleHandle {
idle_queue: Weak<Mutex<Vec<IdleKind>>>,
}

#[derive(Debug)]
enum DeferredOp {
SetSize(Size),
SetPosition(Point),
}

/// This represents different Idle Callback Mechanism
enum IdleKind {
Callback(Box<dyn IdleCallback>),
Token(IdleToken),
DeferredOp(DeferredOp),
}

/// This is the state associated with our custom NSView.
Expand All @@ -156,7 +163,7 @@ impl WindowBuilder {
handler: None,
title: String::new(),
menu: None,
size: Size::new(500.0, 400.0),
size: Size::new(0., 0.),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See issue #1543: because of this change, windows don't appear at all on Mac if their size are not explicitly set.

min_size: None,
position: None,
level: None,
Expand Down Expand Up @@ -220,10 +227,11 @@ impl WindowBuilder {
style_mask |= NSWindowStyleMask::NSResizableWindowMask;
}

let rect = NSRect::new(
NSPoint::new(0., 0.),
NSSize::new(self.size.width, self.size.height),
);
let screen_height = crate::Screen::get_display_rect().height();
let position = self.position.unwrap_or_else(|| Point::new(20., 20.));
let origin = NSPoint::new(position.x, screen_height - position.y - self.size.height); // Flip back

let rect = NSRect::new(origin, NSSize::new(self.size.width, self.size.height));

let window: id = msg_send![WINDOW_CLASS.0, alloc];
let window = window.initWithContentRect_styleMask_backing_defer_(
Expand All @@ -238,7 +246,6 @@ impl WindowBuilder {
window.setContentMinSize_(size);
}

window.cascadeTopLeftFromPoint_(NSPoint::new(20.0, 20.0));
window.setTitle_(make_nsstring(&self.title));

let (view, idle_queue) = make_view(self.handler.expect("view"));
Expand All @@ -260,9 +267,6 @@ impl WindowBuilder {
idle_queue,
};

if let Some(pos) = self.position {
handle.set_position(pos);
}
if let Some(window_state) = self.window_state {
handle.set_window_state(window_state);
}
Expand Down Expand Up @@ -758,6 +762,43 @@ extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) {
}
}

fn run_deferred(this: &mut Object, view_state: &mut ViewState, op: DeferredOp) {
match op {
DeferredOp::SetSize(size) => set_size_deferred(this, view_state, size),
DeferredOp::SetPosition(pos) => set_position_deferred(this, view_state, pos),
}
}

fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) {
unsafe {
let window: id = msg_send![this, window];
let current_frame: NSRect = msg_send![window, frame];
let mut new_frame = current_frame;

// TODO: maintain druid origin (as mac origin is bottom left)
rjwittams marked this conversation as resolved.
Show resolved Hide resolved
new_frame.origin.y -= size.height - current_frame.size.height;
new_frame.size.width = size.width;
new_frame.size.height = size.height;
let () = msg_send![window, setFrame: new_frame display: YES];
}
}

fn set_position_deferred(this: &mut Object, _view_state: &mut ViewState, position: Point) {
unsafe {
// TODO this should be the max y in orig mac coords
rjwittams marked this conversation as resolved.
Show resolved Hide resolved

let window: id = msg_send![this, window];
let frame: NSRect = msg_send![window, frame];

let mut new_frame = frame;
new_frame.origin.x = position.x;
let screen_height = crate::Screen::get_display_rect().height();
new_frame.origin.y = screen_height - position.y - frame.size.height; // Flip back
let () = msg_send![window, setFrame: new_frame display: YES];
log::debug!("set_position_deferred {:?}", position);
}
}

extern "C" fn run_idle(this: &mut Object, _: Sel) {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
Expand All @@ -773,6 +814,7 @@ extern "C" fn run_idle(this: &mut Object, _: Sel) {
IdleKind::Token(it) => {
view_state.handler.as_mut().idle(it);
}
IdleKind::DeferredOp(op) => run_deferred(this, view_state, op),
}
}
}
Expand Down Expand Up @@ -1005,17 +1047,7 @@ impl WindowHandle {

// Need to translate mac y coords, as they start from bottom left
pub fn set_position(&self, position: Point) {
unsafe {
// TODO this should be the max y in orig mac coords
let screen_height = crate::Screen::get_display_rect().height();
let window: id = msg_send![*self.nsview.load(), window];
let frame: NSRect = msg_send![window, frame];

let mut new_frame = frame;
new_frame.origin.x = position.x;
new_frame.origin.y = screen_height - position.y - frame.size.height; // Flip back
let () = msg_send![window, setFrame: new_frame display: YES];
}
self.defer(DeferredOp::SetPosition(position))
}

pub fn get_position(&self) -> Point {
Expand All @@ -1033,6 +1065,34 @@ impl WindowHandle {
}
}

pub fn get_content_insets(&self) -> Insets {
unsafe {
let screen_height = crate::Screen::get_display_rect().height();

let window: id = msg_send![*self.nsview.load(), window];
let clr: NSRect = msg_send![window, contentLayoutRect];

let window_frame_r: NSRect = NSWindow::frame(window);
let content_frame_r: NSRect = NSWindow::convertRectToScreen_(window, clr);

let window_frame_rk = Rect::from_origin_size(
(
window_frame_r.origin.x,
screen_height - window_frame_r.origin.y - window_frame_r.size.height,
),
(window_frame_r.size.width, window_frame_r.size.height),
);
let content_frame_rk = Rect::from_origin_size(
(
content_frame_r.origin.x,
screen_height - content_frame_r.origin.y - content_frame_r.size.height,
),
(content_frame_r.size.width, content_frame_r.size.height),
);
window_frame_rk - content_frame_rk
}
}

pub fn set_level(&self, level: WindowLevel) {
unsafe {
let level = levels::as_raw_window_level(level);
Expand All @@ -1042,14 +1102,7 @@ impl WindowHandle {
}

pub fn set_size(&self, size: Size) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let current_frame: NSRect = msg_send![window, frame];
let mut new_frame = current_frame;
new_frame.size.width = size.width;
new_frame.size.height = size.height;
let () = msg_send![window, setFrame: new_frame display: YES];
}
self.defer(DeferredOp::SetSize(size));
}

pub fn get_size(&self) -> Size {
Expand Down Expand Up @@ -1132,6 +1185,12 @@ impl WindowHandle {
}
}

fn defer(&self, op: DeferredOp) {
if let Some(i) = self.get_idle_handle() {
i.add_idle(IdleKind::DeferredOp(op))
}
}

/// Get a handle that can be used to schedule an idle task.
pub fn get_idle_handle(&self) -> Option<IdleHandle> {
if self.nsview.load().is_null() {
Expand All @@ -1154,16 +1213,7 @@ impl WindowHandle {
unsafe impl Send for IdleHandle {}

impl IdleHandle {
/// Add an idle handler, which is called (once) when the message loop
/// is empty. The idle handler will be run from the main UI thread, and
/// won't be scheduled if the associated view has been dropped.
///
/// Note: the name "idle" suggests that it will be scheduled with a lower
/// priority than other UI events, but that's not necessarily the case.
pub fn add_idle_callback<F>(&self, callback: F)
where
F: FnOnce(&dyn Any) + Send + 'static,
{
fn add_idle(&self, idle: IdleKind) {
if let Some(queue) = self.idle_queue.upgrade() {
let mut queue = queue.lock().expect("queue lock");
if queue.is_empty() {
Expand All @@ -1174,23 +1224,25 @@ impl IdleHandle {
withObject: nil waitUntilDone: NO);
}
}
queue.push(IdleKind::Callback(Box::new(callback)));
queue.push(idle);
}
}

/// Add an idle handler, which is called (once) when the message loop
/// is empty. The idle handler will be run from the main UI thread, and
/// won't be scheduled if the associated view has been dropped.
///
/// Note: the name "idle" suggests that it will be scheduled with a lower
/// priority than other UI events, but that's not necessarily the case.
pub fn add_idle_callback<F>(&self, callback: F)
where
F: FnOnce(&dyn Any) + Send + 'static,
{
self.add_idle(IdleKind::Callback(Box::new(callback)));
}

pub fn add_idle_token(&self, token: IdleToken) {
if let Some(queue) = self.idle_queue.upgrade() {
let mut queue = queue.lock().expect("queue lock");
if queue.is_empty() {
unsafe {
let nsview = self.nsview.load();
// Note: the nsview might be nil here if the window has been dropped, but that's ok.
let () = msg_send!(*nsview, performSelectorOnMainThread: sel!(runIdle)
withObject: nil waitUntilDone: NO);
}
}
queue.push(IdleKind::Token(token));
}
self.add_idle(IdleKind::Token(token));
}
}

Expand Down
7 changes: 6 additions & 1 deletion druid-shell/src/platform/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use instant::Instant;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};

use crate::piet::{PietText, RenderContext};

Expand Down Expand Up @@ -477,6 +477,11 @@ impl WindowHandle {
Size::new(0.0, 0.0)
}

pub fn get_content_insets(&self) -> Insets {
log::warn!("WindowHandle::get_content_insets unimplemented for web.");
Insets::ZERO
}

pub fn set_window_state(&self, _state: window::WindowState) {
log::warn!("WindowHandle::set_window_state unimplemented for web.");
}
Expand Down
7 changes: 6 additions & 1 deletion druid-shell/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use winapi::um::winuser::*;
use piet_common::d2d::{D2DFactory, DeviceContext};
use piet_common::dwrite::DwriteFactory;

use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::piet::{Piet, PietText, RenderContext};

use super::accels::register_accel;
Expand Down Expand Up @@ -1657,6 +1657,11 @@ impl WindowHandle {
Point::new(0.0, 0.0)
}

pub fn get_content_insets(&self) -> Insets {
log::warn!("WindowHandle::get_content_insets unimplemented for windows.");
Insets::ZERO
}

// Sets the size of the window in DP
pub fn set_size(&self, size: Size) {
self.defer(DeferredOp::SetSize(size));
Expand Down
7 changes: 6 additions & 1 deletion druid-shell/src/platform/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::common_util::IdleCallback;
use crate::dialog::FileDialogOptions;
use crate::error::Error as ShellError;
use crate::keyboard::{KeyEvent, KeyState, Modifiers};
use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent};
use crate::piet::{Piet, PietText, RenderContext};
use crate::region::Region;
Expand Down Expand Up @@ -1404,6 +1404,11 @@ impl WindowHandle {
Point::new(0.0, 0.0)
}

pub fn get_content_insets(&self) -> Insets {
log::warn!("WindowHandle::get_content_insets unimplemented for X11 platforms.");
Insets::ZERO
}

pub fn set_level(&self, _level: WindowLevel) {
log::warn!("WindowHandle::set_level is currently unimplemented for X11 platforms.");
}
Expand Down
10 changes: 8 additions & 2 deletions druid-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::common_util::Counter;
use crate::dialog::{FileDialogOptions, FileInfo};
use crate::error::Error;
use crate::keyboard::KeyEvent;
use crate::kurbo::{Point, Rect, Size};
use crate::kurbo::{Insets, Point, Rect, Size};
use crate::menu::Menu;
use crate::mouse::{Cursor, CursorDesc, MouseEvent};
use crate::platform::window as platform;
Expand Down Expand Up @@ -194,12 +194,18 @@ impl WindowHandle {
self.0.set_position(position.into())
}

/// Returns the position of the window in [pixels](crate::Scale), relative to the origin of the
/// Returns the position of the top left corner of the window in [pixels](crate::Scale), relative to the origin of the
/// virtual screen.
pub fn get_position(&self) -> Point {
self.0.get_position()
}

/// Returns the insets of the window content from its position and size in [pixels](crate::Scale).
/// This is to account for any window system provided chrome, eg. title bars.
rjwittams marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_content_insets(&self) -> Insets {
self.0.get_content_insets()
}

/// Set the window's size in [display points](crate::Scale).
///
/// The actual window size in pixels will depend on the platform DPI settings.
Expand Down
1 change: 1 addition & 0 deletions druid/examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub fn main() {

// start the application
AppLauncher::with_window(main_window)
.use_simple_logger()
.launch(initial_state)
.expect("Failed to launch application");
}
Expand Down
Loading