mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
On macOS, add tabbing APIs
This should let the users control macOS tabbing and allow to create windows in tab. Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
This commit is contained in:
parent
b63164645b
commit
c5941d105f
|
@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- On macOS, add tabbing APIs on `WindowExtMacOS`.
|
||||||
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
|
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
|
||||||
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
|
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
|
||||||
- Implement `PartialOrd` and `Ord` for `KeyCode` and `NativeKeyCode`.
|
- Implement `PartialOrd` and `Ord` for `KeyCode` and `NativeKeyCode`.
|
||||||
|
|
105
examples/window_tabbing.rs
Normal file
105
examples/window_tabbing.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use std::{collections::HashMap, num::NonZeroUsize};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use simple_logger::SimpleLogger;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use winit::{
|
||||||
|
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||||
|
event_loop::EventLoop,
|
||||||
|
keyboard::Key,
|
||||||
|
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
|
||||||
|
window::{Window, WindowBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[path = "util/fill.rs"]
|
||||||
|
mod fill;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn main() {
|
||||||
|
SimpleLogger::new().init().unwrap();
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
|
let mut windows = HashMap::new();
|
||||||
|
let window = Window::new(&event_loop).unwrap();
|
||||||
|
println!("Opened a new window: {:?}", window.id());
|
||||||
|
windows.insert(window.id(), window);
|
||||||
|
|
||||||
|
println!("Press N to open a new window.");
|
||||||
|
|
||||||
|
event_loop.run(move |event, event_loop, control_flow| {
|
||||||
|
control_flow.set_wait();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { event, window_id } => {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
println!("Window {window_id:?} has received the signal to close");
|
||||||
|
|
||||||
|
// This drops the window, causing it to close.
|
||||||
|
windows.remove(&window_id);
|
||||||
|
|
||||||
|
if windows.is_empty() {
|
||||||
|
control_flow.set_exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::Resized(_) => {
|
||||||
|
if let Some(window) = windows.get(&window_id) {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput {
|
||||||
|
event:
|
||||||
|
KeyEvent {
|
||||||
|
state: ElementState::Pressed,
|
||||||
|
logical_key,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
is_synthetic: false,
|
||||||
|
..
|
||||||
|
} => match logical_key.as_ref() {
|
||||||
|
Key::Character("t") => {
|
||||||
|
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
|
||||||
|
let window = WindowBuilder::new()
|
||||||
|
.with_tabbing_identifier(&tabbing_id)
|
||||||
|
.build(event_loop)
|
||||||
|
.unwrap();
|
||||||
|
println!("Added a new tab: {:?}", window.id());
|
||||||
|
windows.insert(window.id(), window);
|
||||||
|
}
|
||||||
|
Key::Character("w") => {
|
||||||
|
let _ = windows.remove(&window_id);
|
||||||
|
}
|
||||||
|
Key::ArrowRight => {
|
||||||
|
windows.get(&window_id).unwrap().select_next_tab();
|
||||||
|
}
|
||||||
|
Key::ArrowLeft => {
|
||||||
|
windows.get(&window_id).unwrap().select_previous_tab();
|
||||||
|
}
|
||||||
|
Key::Character(ch) => {
|
||||||
|
if let Ok(index) = ch.parse::<NonZeroUsize>() {
|
||||||
|
windows.get(&window_id).unwrap().select_tab_at_index(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::RedrawRequested(window_id) => {
|
||||||
|
if let Some(window) = windows.get(&window_id) {
|
||||||
|
fill::fill_window(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
fn main() {
|
||||||
|
println!("This example is only supported on MacOS");
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use std::os::raw::c_void;
|
use std::{num::NonZeroUsize, os::raw::c_void};
|
||||||
|
|
||||||
use objc2::rc::Id;
|
use objc2::rc::Id;
|
||||||
|
|
||||||
|
@ -38,6 +38,33 @@ pub trait WindowExtMacOS {
|
||||||
/// Sets whether or not the window has shadow.
|
/// Sets whether or not the window has shadow.
|
||||||
fn set_has_shadow(&self, has_shadow: bool);
|
fn set_has_shadow(&self, has_shadow: bool);
|
||||||
|
|
||||||
|
/// Sets whether the system can automatically organize windows into tabs.
|
||||||
|
///
|
||||||
|
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
|
||||||
|
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
|
||||||
|
|
||||||
|
/// Returns whether the system can automatically organize windows into tabs.
|
||||||
|
fn allows_automatic_window_tabbing(&self) -> bool;
|
||||||
|
|
||||||
|
/// Group windows together by using the same tabbing identifier.
|
||||||
|
///
|
||||||
|
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||||
|
fn set_tabbing_identifier(&self, identifier: &str);
|
||||||
|
|
||||||
|
/// Returns the window's tabbing identifier.
|
||||||
|
fn tabbing_identifier(&self) -> String;
|
||||||
|
|
||||||
|
/// Select next tab.
|
||||||
|
fn select_next_tab(&self);
|
||||||
|
|
||||||
|
/// Select previous tab.
|
||||||
|
fn select_previous_tab(&self);
|
||||||
|
|
||||||
|
/// Select the tab with the given index.
|
||||||
|
///
|
||||||
|
/// Will no-op when the index is out of bounds.
|
||||||
|
fn select_tab_at_index(&self, index: NonZeroUsize);
|
||||||
|
|
||||||
/// Get the window's edit state.
|
/// Get the window's edit state.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -100,6 +127,41 @@ impl WindowExtMacOS for Window {
|
||||||
self.window.set_has_shadow(has_shadow)
|
self.window.set_has_shadow(has_shadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
||||||
|
self.window.set_allows_automatic_window_tabbing(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn allows_automatic_window_tabbing(&self) -> bool {
|
||||||
|
self.window.allows_automatic_window_tabbing()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_tabbing_identifier(&self, identifier: &str) {
|
||||||
|
self.window.set_tabbing_identifier(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tabbing_identifier(&self) -> String {
|
||||||
|
self.window.tabbing_identifier()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn select_next_tab(&self) {
|
||||||
|
self.window.select_next_tab();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn select_previous_tab(&self) {
|
||||||
|
self.window.select_previous_tab();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn select_tab_at_index(&self, index: NonZeroUsize) {
|
||||||
|
self.window.select_tab_at_index(index);
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_document_edited(&self) -> bool {
|
fn is_document_edited(&self) -> bool {
|
||||||
self.window.is_document_edited()
|
self.window.is_document_edited()
|
||||||
|
@ -161,6 +223,14 @@ pub trait WindowBuilderExtMacOS {
|
||||||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
||||||
/// Window accepts click-through mouse events.
|
/// Window accepts click-through mouse events.
|
||||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
||||||
|
/// Whether the window could do automatic window tabbing.
|
||||||
|
///
|
||||||
|
/// The default is `true`.
|
||||||
|
fn with_automatic_window_tabbing(self, automatic_tabbing: bool) -> WindowBuilder;
|
||||||
|
/// Defines the window tabbing identifier.
|
||||||
|
///
|
||||||
|
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||||
|
fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder;
|
||||||
/// Set how the <kbd>Option</kbd> keys are interpreted.
|
/// Set how the <kbd>Option</kbd> keys are interpreted.
|
||||||
///
|
///
|
||||||
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||||
|
@ -225,6 +295,20 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_automatic_window_tabbing(mut self, automatic_tabbing: bool) -> WindowBuilder {
|
||||||
|
self.platform_specific.allows_automatic_window_tabbing = automatic_tabbing;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder {
|
||||||
|
self.platform_specific
|
||||||
|
.tabbing_identifier
|
||||||
|
.replace(tabbing_identifier.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
|
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
|
||||||
self.platform_specific.option_as_alt = option_as_alt;
|
self.platform_specific.option_as_alt = option_as_alt;
|
||||||
|
|
|
@ -24,6 +24,7 @@ mod menu_item;
|
||||||
mod pasteboard;
|
mod pasteboard;
|
||||||
mod responder;
|
mod responder;
|
||||||
mod screen;
|
mod screen;
|
||||||
|
mod tab_group;
|
||||||
mod text_input_context;
|
mod text_input_context;
|
||||||
mod version;
|
mod version;
|
||||||
mod view;
|
mod view;
|
||||||
|
@ -49,6 +50,7 @@ pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPastebo
|
||||||
pub(crate) use self::responder::NSResponder;
|
pub(crate) use self::responder::NSResponder;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
|
pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
|
||||||
|
pub(crate) use self::tab_group::NSWindowTabGroup;
|
||||||
pub(crate) use self::text_input_context::NSTextInputContext;
|
pub(crate) use self::text_input_context::NSTextInputContext;
|
||||||
pub(crate) use self::version::NSAppKitVersion;
|
pub(crate) use self::version::NSAppKitVersion;
|
||||||
pub(crate) use self::view::{NSTrackingRectTag, NSView};
|
pub(crate) use self::view::{NSTrackingRectTag, NSView};
|
||||||
|
|
28
src/platform_impl/macos/appkit/tab_group.rs
Normal file
28
src/platform_impl/macos/appkit/tab_group.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use objc2::foundation::{NSArray, NSObject};
|
||||||
|
use objc2::rc::{Id, Shared};
|
||||||
|
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||||
|
|
||||||
|
use super::NSWindow;
|
||||||
|
|
||||||
|
extern_class!(
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub(crate) struct NSWindowTabGroup;
|
||||||
|
|
||||||
|
unsafe impl ClassType for NSWindowTabGroup {
|
||||||
|
type Super = NSObject;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
extern_methods!(
|
||||||
|
unsafe impl NSWindowTabGroup {
|
||||||
|
#[sel(selectNextTab)]
|
||||||
|
pub fn selectNextTab(&self);
|
||||||
|
#[sel(selectPreviousTab)]
|
||||||
|
pub fn selectPreviousTab(&self);
|
||||||
|
pub fn tabbedWindows(&self) -> Id<NSArray<NSWindow, Shared>, Shared> {
|
||||||
|
unsafe { msg_send_id![self, windows] }
|
||||||
|
}
|
||||||
|
#[sel(setSelectedWindow:)]
|
||||||
|
pub fn setSelectedWindow(&self, window: &NSWindow);
|
||||||
|
}
|
||||||
|
);
|
|
@ -6,7 +6,9 @@ use objc2::rc::{Id, Shared};
|
||||||
use objc2::runtime::Object;
|
use objc2::runtime::Object;
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||||
|
|
||||||
use super::{NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView};
|
use super::{
|
||||||
|
NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView, NSWindowTabGroup,
|
||||||
|
};
|
||||||
|
|
||||||
extern_class!(
|
extern_class!(
|
||||||
/// Main-Thread-Only!
|
/// Main-Thread-Only!
|
||||||
|
@ -171,6 +173,12 @@ extern_methods!(
|
||||||
#[sel(setLevel:)]
|
#[sel(setLevel:)]
|
||||||
pub fn setLevel(&self, level: NSWindowLevel);
|
pub fn setLevel(&self, level: NSWindowLevel);
|
||||||
|
|
||||||
|
#[sel(setAllowsAutomaticWindowTabbing:)]
|
||||||
|
pub fn setAllowsAutomaticWindowTabbing(val: bool);
|
||||||
|
|
||||||
|
#[sel(setTabbingIdentifier:)]
|
||||||
|
pub fn setTabbingIdentifier(&self, identifier: &NSString);
|
||||||
|
|
||||||
#[sel(setDocumentEdited:)]
|
#[sel(setDocumentEdited:)]
|
||||||
pub fn setDocumentEdited(&self, val: bool);
|
pub fn setDocumentEdited(&self, val: bool);
|
||||||
|
|
||||||
|
@ -201,6 +209,20 @@ extern_methods!(
|
||||||
#[sel(isZoomed)]
|
#[sel(isZoomed)]
|
||||||
pub fn isZoomed(&self) -> bool;
|
pub fn isZoomed(&self) -> bool;
|
||||||
|
|
||||||
|
#[sel(allowsAutomaticWindowTabbing)]
|
||||||
|
pub fn allowsAutomaticWindowTabbing() -> bool;
|
||||||
|
|
||||||
|
#[sel(selectNextTab)]
|
||||||
|
pub fn selectNextTab(&self);
|
||||||
|
|
||||||
|
pub fn tabbingIdentifier(&self) -> Id<NSString, Shared> {
|
||||||
|
unsafe { msg_send_id![self, tabbingIdentifier] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tabGroup(&self) -> Id<NSWindowTabGroup, Shared> {
|
||||||
|
unsafe { msg_send_id![self, tabGroup] }
|
||||||
|
}
|
||||||
|
|
||||||
#[sel(isDocumentEdited)]
|
#[sel(isDocumentEdited)]
|
||||||
pub fn isDocumentEdited(&self) -> bool;
|
pub fn isDocumentEdited(&self) -> bool;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::f64;
|
use std::f64;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
@ -82,6 +83,8 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub disallow_hidpi: bool,
|
pub disallow_hidpi: bool,
|
||||||
pub has_shadow: bool,
|
pub has_shadow: bool,
|
||||||
pub accepts_first_mouse: bool,
|
pub accepts_first_mouse: bool,
|
||||||
|
pub allows_automatic_window_tabbing: bool,
|
||||||
|
pub tabbing_identifier: Option<String>,
|
||||||
pub option_as_alt: OptionAsAlt,
|
pub option_as_alt: OptionAsAlt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +101,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
disallow_hidpi: false,
|
disallow_hidpi: false,
|
||||||
has_shadow: true,
|
has_shadow: true,
|
||||||
accepts_first_mouse: true,
|
accepts_first_mouse: true,
|
||||||
|
allows_automatic_window_tabbing: true,
|
||||||
|
tabbing_identifier: None,
|
||||||
option_as_alt: Default::default(),
|
option_as_alt: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,6 +362,12 @@ impl WinitWindow {
|
||||||
this.setTitle(&NSString::from_str(&attrs.title));
|
this.setTitle(&NSString::from_str(&attrs.title));
|
||||||
this.setAcceptsMouseMovedEvents(true);
|
this.setAcceptsMouseMovedEvents(true);
|
||||||
|
|
||||||
|
if let Some(identifier) = pl_attrs.tabbing_identifier {
|
||||||
|
this.setTabbingIdentifier(&NSString::from_str(&identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
NSWindow::setAllowsAutomaticWindowTabbing(pl_attrs.allows_automatic_window_tabbing);
|
||||||
|
|
||||||
if attrs.content_protected {
|
if attrs.content_protected {
|
||||||
this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
||||||
}
|
}
|
||||||
|
@ -1394,6 +1405,46 @@ impl WindowExtMacOS for WinitWindow {
|
||||||
self.setHasShadow(has_shadow)
|
self.setHasShadow(has_shadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
||||||
|
NSWindow::setAllowsAutomaticWindowTabbing(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn allows_automatic_window_tabbing(&self) -> bool {
|
||||||
|
NSWindow::allowsAutomaticWindowTabbing()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_tabbing_identifier(&self, identifier: &str) {
|
||||||
|
self.setTabbingIdentifier(&NSString::from_str(identifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tabbing_identifier(&self) -> String {
|
||||||
|
self.tabbingIdentifier().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn select_next_tab(&self) {
|
||||||
|
self.tabGroup().selectNextTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn select_previous_tab(&self) {
|
||||||
|
self.tabGroup().selectPreviousTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn select_tab_at_index(&self, index: NonZeroUsize) {
|
||||||
|
let tab_group = self.tabGroup();
|
||||||
|
let windows = tab_group.tabbedWindows();
|
||||||
|
let index = index.get() - 1;
|
||||||
|
if index < windows.len() {
|
||||||
|
tab_group.setSelectedWindow(&windows[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_document_edited(&self) -> bool {
|
fn is_document_edited(&self) -> bool {
|
||||||
self.isDocumentEdited()
|
self.isDocumentEdited()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue