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

implement focusable attribute for Window #4124

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions src/platform_impl/apple/appkit/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ pub struct ViewState {

marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool,
focusable: Cell<bool>,

/// The state of the `Option` as `Alt`.
option_as_alt: Cell<OptionAsAlt>,
Expand Down Expand Up @@ -777,13 +778,20 @@ define_class!(
trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse
}

#[method(shouldDelayWindowOrderingForEvent:)]
fn should_delay_window_ordering_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("shouldDelayWindowOrderingForEvent:");
!self.ivars().focusable.get()
}
}
);

impl WinitView {
pub(super) fn new(
app_state: &Rc<AppState>,
accepts_first_mouse: bool,
focusable: bool,
option_as_alt: OptionAsAlt,
mtm: MainThreadMarker,
) -> Retained<Self> {
Expand All @@ -801,6 +809,7 @@ impl WinitView {
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
focusable: focusable.into(),
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I prefer Cell::new explicitly.

Suggested change
focusable: focusable.into(),
focusable: Cell::new(focusable),

option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
Expand Down Expand Up @@ -884,6 +893,10 @@ impl WinitView {
input_context.invalidateCharacterCoordinates();
}

pub(super) fn set_focusable(&self, focusable: bool) {
self.ivars().focusable.set(focusable);
}

/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
pub(super) fn reset_modifiers(&self) {
if !self.ivars().modifiers.get().state().is_empty() {
Expand Down Expand Up @@ -1029,6 +1042,12 @@ impl WinitView {
}

fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
if !self.ivars().focusable.get() {
let mtm = MainThreadMarker::from(self);
unsafe {
NSApplication::sharedApplication(mtm).preventWindowOrdering();
Copy link
Member

Choose a reason for hiding this comment

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

Nit: It'd be nice with a comment here detailing why preventWindowOrdering, an application-level method, is fine to call inside NSView.

}
}
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
let button = mouse_button(event);

Expand Down
27 changes: 23 additions & 4 deletions src/platform_impl/apple/appkit/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ pub(crate) struct State {
decorations: Cell<bool>,
resizable: Cell<bool>,
maximized: Cell<bool>,
focusable: Cell<bool>,
Copy link
Member

Choose a reason for hiding this comment

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

Avoid this state, query it from view.ivars().focusable instead.


/// Presentation options saved before entering `set_simple_fullscreen`, and
/// restored upon exiting it. Also used when transitioning from Borderless to
Expand Down Expand Up @@ -723,6 +724,7 @@ fn new_window(
let view = WinitView::new(
app_state,
attrs.platform_specific.accepts_first_mouse,
attrs.focusable,
attrs.platform_specific.option_as_alt,
mtm,
);
Expand Down Expand Up @@ -829,6 +831,7 @@ impl WindowDelegate {
decorations: Cell::new(attrs.decorations),
resizable: Cell::new(attrs.resizable),
maximized: Cell::new(attrs.maximized),
focusable: Cell::new(attrs.focusable),
save_presentation_opts: Cell::new(None),
initial_fullscreen: Cell::new(attrs.fullscreen.is_some()),
fullscreen: RefCell::new(None),
Expand Down Expand Up @@ -877,7 +880,7 @@ impl WindowDelegate {
// state, since otherwise we'll briefly see the window at normal size
// before it transitions.
if attrs.visible {
if attrs.active {
if attrs.active && attrs.focusable {
// Tightly linked with `app_state::window_activation_hack`
window.makeKeyAndOrderFront(None);
} else {
Expand Down Expand Up @@ -996,9 +999,14 @@ impl WindowDelegate {
}

pub fn set_visible(&self, visible: bool) {
match visible {
true => self.window().makeKeyAndOrderFront(None),
false => self.window().orderOut(None),
if visible {
if self.is_focusable() == Some(true) {
self.window().makeKeyAndOrderFront(None);
} else {
self.window().orderFront(None);
}
} else {
self.window().orderOut(None);
}
}

Expand All @@ -1007,6 +1015,17 @@ impl WindowDelegate {
Some(self.window().isVisible())
}

#[inline]
pub fn set_focusable(&self, focusable: bool) {
self.ivars().focusable.set(focusable);
self.view().set_focusable(focusable);
}

#[inline]
pub fn is_focusable(&self) -> Option<bool> {
Some(self.ivars().focusable.get())
}

pub fn request_redraw(&self) {
self.ivars().app_state.queue_redraw(window_id(self.window()));
}
Expand Down
9 changes: 9 additions & 0 deletions src/platform_impl/apple/uikit/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ impl Inner {
None
}

pub fn set_focusable(&self, focusable: bool) {
warn!("`Window::set_focusable` is ignored on iOS");
}

pub fn is_focusable(&self) -> Option<bool> {
warn!("`Window::is_focusable` is ignored on iOS");
None
}

pub fn request_redraw(&self) {
if self.gl_or_metal_backed {
let mtm = MainThreadMarker::new().unwrap();
Expand Down
6 changes: 6 additions & 0 deletions src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,12 @@ impl CoreWindow for Window {
None
}

pub fn set_focusable(&self, focusable: bool) {}

pub fn is_focusable(&self) -> Option<bool> {
None
}

fn set_resizable(&self, resizable: bool) {
if self.window_state.lock().unwrap().set_resizable(resizable) {
// NOTE: Requires commit to be applied.
Expand Down
25 changes: 25 additions & 0 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ impl CoreWindow for Window {
self.0.is_visible()
}

fn set_focusable(&self, focusable: bool) {
self.0.set_focusable(focusable)
}

fn is_focusable(&self) -> Option<bool> {
self.0.is_focusable()
}

fn set_resizable(&self, resizable: bool) {
self.0.set_resizable(resizable);
}
Expand Down Expand Up @@ -346,6 +354,7 @@ pub struct SharedState {
pub inner_position: Option<(i32, i32)>,
pub inner_position_rel_parent: Option<(i32, i32)>,
pub is_resizable: bool,
pub is_focusable: bool,
pub is_decorated: bool,
pub last_monitor: X11MonitorHandle,
pub dpi_adjusted: Option<(u32, u32)>,
Expand Down Expand Up @@ -385,6 +394,7 @@ impl SharedState {
visibility,

is_resizable: window_attributes.resizable,
is_focusable: true,
is_decorated: window_attributes.decorations,
cursor_pos: None,
size: None,
Expand Down Expand Up @@ -1484,6 +1494,21 @@ impl UnownedWindow {
Some(self.shared_state_lock().visibility == Visibility::Yes)
}

#[inline]
pub fn set_focusable(&self, focusable: bool) {
let mut hints = WmHints::new();
hints.input = Some(focusable);
if let Ok(cookie) = hints.set(self.xconn.xcb_connection(), self.xwindow) {
cookie.ignore_error();
}
self.shared_state_lock().is_focusable = focusable;
}

#[inline]
pub fn is_focusable(&self) -> Option<bool> {
Some(self.shared_state_lock().is_focusable)
}

fn update_cached_frame_extents(&self) {
let extents = self.xconn.get_frame_extents_heuristic(self.xwindow, self.root);
self.shared_state_lock().frame_extents = Some(extents);
Expand Down
9 changes: 9 additions & 0 deletions src/platform_impl/orbital/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,15 @@ impl CoreWindow for Window {
Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false))
}

pub fn set_focusable(&self, focusable: bool) {
warn!("`Window::set_focusable` is ignored on Orbital");
}

pub fn is_focusable(&self) -> Option<bool> {
warn!("`Window::is_focusable` is ignored on Orbital");
None
}

#[inline]
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
Expand Down
9 changes: 9 additions & 0 deletions src/platform_impl/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@ impl RootWindow for Window {
None
}

pub fn set_focusable(&self, focusable: bool) {
warn!("`Window::set_focusable` is ignored on Web");
}

pub fn is_focusable(&self) -> Option<bool> {
warn!("`Window::is_focusable` is ignored on Web");
None
}

fn set_resizable(&self, _: bool) {
// Intentionally a no-op: users can't resize canvas elements
}
Expand Down
17 changes: 17 additions & 0 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,22 @@ impl CoreWindow for Window {
Some(unsafe { IsWindowVisible(self.window) == 1 })
}

pub fn set_focusable(&self, focusable: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::FOCUSABLE, focusable)
});
});
}

pub fn is_focusable(&self) -> Option<bool> {
let window_flags = self.window_state_lock().window_flags;
Some(window_flags.contains(WindowFlags::FOCUSABLE))
}

fn request_redraw(&self) {
// NOTE: mark that we requested a redraw to handle requests during `WM_PAINT` handling.
self.window_state.lock().unwrap().redraw_requested = true;
Expand Down Expand Up @@ -1258,6 +1274,7 @@ unsafe fn init(
unsafe { register_window_class(&class_name) };

let mut window_flags = WindowFlags::empty();
window_flags.set(WindowFlags::FOCUSABLE, attributes.focusable);
window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations);
window_flags.set(
WindowFlags::MARKER_UNDECORATED_SHADOW,
Expand Down
35 changes: 21 additions & 14 deletions src/platform_impl/windows/window_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER,
SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, SW_SHOWNOACTIVATE, WINDOWPLACEMENT,
WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN,
WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP,
WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE,
WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE,
WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOACTIVATE,
WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE,
WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX,
WS_SYSMENU, WS_VISIBLE,
};

use crate::dpi::{PhysicalPosition, PhysicalSize, Size};
Expand Down Expand Up @@ -97,33 +98,34 @@ bitflags! {
const CHILD = 1 << 10;
const MAXIMIZED = 1 << 11;
const POPUP = 1 << 12;
const FOCUSABLE = 1 << 14;

/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
/// included here to make masking easier.
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 13;
const MARKER_BORDERLESS_FULLSCREEN = 1 << 14;
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 15;
const MARKER_BORDERLESS_FULLSCREEN = 1 << 16;

/// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`.
/// In most cases, it's okay to let those parameters change the state. However, when we're
/// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to
/// effect our stored state, because the purpose of `apply_diff` is to update the actual
/// window's state to match our stored state. This controls whether to accept those changes.
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 15;
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 17;

const MARKER_IN_SIZE_MOVE = 1 << 16;
const MARKER_IN_SIZE_MOVE = 1 << 18;

const MINIMIZED = 1 << 17;
const MINIMIZED = 1 << 19;

const IGNORE_CURSOR_EVENT = 1 << 18;
const IGNORE_CURSOR_EVENT = 1 << 20;

/// Fully decorated window (incl. caption, border and drop shadow).
const MARKER_DECORATIONS = 1 << 19;
const MARKER_DECORATIONS = 1 << 21;
/// Drop shadow for undecorated windows.
const MARKER_UNDECORATED_SHADOW = 1 << 20;
const MARKER_UNDECORATED_SHADOW = 1 << 22;

const MARKER_ACTIVATE = 1 << 21;
const MARKER_ACTIVATE = 1 << 23;

const CLIP_CHILDREN = 1 << 22;
const CLIP_CHILDREN = 1 << 24;

const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits();
}
Expand Down Expand Up @@ -292,6 +294,9 @@ impl WindowFlags {
if self.contains(WindowFlags::POPUP) {
style |= WS_POPUP;
}
if !self.contains(WindowFlags::FOCUSABLE) {
style_ex |= WS_EX_NOACTIVATE;
}
if self.contains(WindowFlags::MINIMIZED) {
style |= WS_MINIMIZE;
}
Expand Down Expand Up @@ -326,7 +331,9 @@ impl WindowFlags {
}

if new.contains(WindowFlags::VISIBLE) {
let flag = if !self.contains(WindowFlags::MARKER_ACTIVATE) {
let flag = if !new.contains(WindowFlags::FOCUSABLE) {
SW_SHOWNOACTIVATE
} else if !self.contains(WindowFlags::MARKER_ACTIVATE) {
self.set(WindowFlags::MARKER_ACTIVATE, true);
SW_SHOWNOACTIVATE
} else {
Expand Down
Loading
Loading