Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
24 changes: 19 additions & 5 deletions Examples/Sources/WindowingExample/WindowingApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,11 @@ struct SheetDemo: View {
@HotReloadable
struct WindowingApp: App {
@State var title = "My window"
@State var resizable = false
@State var resizable = true
@State var toggle = false
@State var enforceMaxSize = true
@State var closable = true
@State var minimizable = true

var body: some Scene {
WindowGroup(title) {
Expand All @@ -174,8 +177,19 @@ struct WindowingApp: App {
}

Button(resizable ? "Disable resizing" : "Enable resizing") {
resizable = !resizable
resizable.toggle()
}
.windowResizeBehavior(resizable ? .enabled : .disabled)

Button(closable ? "Disable closing" : "Enable closing") {
closable.toggle()
}
.windowDismissBehavior(closable ? .enabled : .disabled)

Button(minimizable ? "Disable minimizing" : "Enable minimizing") {
minimizable.toggle()
}
.windowMinimizeBehavior(minimizable ? .enabled : .disabled)

Image(Bundle.module.bundleURL.appendingPathComponent("Banner.png"))
.resizable()
Expand All @@ -200,7 +214,6 @@ struct WindowingApp: App {
}
}
.defaultSize(width: 500, height: 500)
.windowResizability(resizable ? .contentMinSize : .contentSize)
.commands {
CommandMenu("Demo menu") {
Button("Menu item") {}
Expand All @@ -218,10 +231,12 @@ struct WindowingApp: App {
#hotReloadable {
Text("This a secondary window!")
.padding(10)

Toggle("Enforce max size", isOn: $enforceMaxSize)
}
}
.defaultSize(width: 200, height: 200)
.windowResizability(.contentMinSize)
.windowResizability(enforceMaxSize ? .contentSize : .contentMinSize)

WindowGroup("Tertiary window") {
#hotReloadable {
Expand All @@ -230,7 +245,6 @@ struct WindowingApp: App {
}
}
.defaultSize(width: 200, height: 200)
.windowResizability(.contentMinSize)
#endif
}
}
34 changes: 30 additions & 4 deletions Sources/AppKitBackend/AppKitBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,18 @@ public final class AppKitBackend: AppBackend {
window.setContentSize(NSSize(width: newSize.x, height: newSize.y))
}

public func setMinimumSize(ofWindow window: Window, to minimumSize: SIMD2<Int>) {
window.contentMinSize.width = CGFloat(minimumSize.x)
window.contentMinSize.height = CGFloat(minimumSize.y)
public func setSizeLimits(
ofWindow window: Window,
minimum minimumSize: SIMD2<Int>,
maximum maximumSize: SIMD2<Int>?
) {
window.contentMinSize = CGSize(width: minimumSize.x, height: minimumSize.y)
window.contentMaxSize =
if let maximumSize {
CGSize(width: maximumSize.x, height: maximumSize.y)
} else {
CGSize(width: Double.infinity, height: .infinity)
}
}

public func setResizeHandler(
Expand All @@ -103,7 +112,24 @@ public final class AppKitBackend: AppBackend {
window.title = title
}

public func setResizability(ofWindow window: Window, to resizable: Bool) {
public func setBehaviors(
ofWindow window: Window,
closable: Bool,
minimizable: Bool,
resizable: Bool
) {
if closable {
window.styleMask.insert(.closable)
} else {
window.styleMask.remove(.closable)
}

if minimizable {
window.styleMask.insert(.miniaturizable)
} else {
window.styleMask.remove(.miniaturizable)
}

if resizable {
window.styleMask.insert(.resizable)
} else {
Expand Down
19 changes: 17 additions & 2 deletions Sources/DummyBackend/DummyBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ public final class DummyBackend: AppBackend {

public var size: SIMD2<Int>
public var minimumSize: SIMD2<Int> = .zero
public var maximumSize: SIMD2<Int>?
public var title = "Window"
public var resizable = true
public var closable = true
public var minimizable = true
public var content: Widget?
public var resizeHandler: ((SIMD2<Int>) -> Void)?

Expand Down Expand Up @@ -264,7 +267,14 @@ public final class DummyBackend: AppBackend {
window.title = title
}

public func setResizability(ofWindow window: Window, to resizable: Bool) {
public func setBehaviors(
ofWindow window: Window,
closable: Bool,
minimizable: Bool,
resizable: Bool
) {
window.closable = closable
window.minimizable = minimizable
window.resizable = resizable
}

Expand All @@ -284,8 +294,13 @@ public final class DummyBackend: AppBackend {
window.size = newSize
}

public func setMinimumSize(ofWindow window: Window, to minimumSize: SIMD2<Int>) {
public func setSizeLimits(
ofWindow window: Window,
minimum minimumSize: SIMD2<Int>,
maximum maximumSize: SIMD2<Int>?
) {
window.minimumSize = minimumSize
window.maximumSize = maximumSize
}

public func setResizeHandler(ofWindow window: Window, to action: @escaping (SIMD2<Int>) -> Void)
Expand Down
1 change: 1 addition & 0 deletions Sources/Gtk/Widgets/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ open class Window: Widget {

@GObjectProperty(named: "title") public var title: String?
@GObjectProperty(named: "resizable") public var resizable: Bool
@GObjectProperty(named: "deletable") public var closable: Bool
@GObjectProperty(named: "modal") public var isModal: Bool
@GObjectProperty(named: "decorated") public var isDecorated: Bool
@GObjectProperty(named: "destroy-with-parent") public var destroyWithParent: Bool
Expand Down
1 change: 1 addition & 0 deletions Sources/Gtk3/Widgets/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ open class Window: Bin {

@GObjectProperty(named: "title") public var title: String?
@GObjectProperty(named: "resizable") public var resizable: Bool
@GObjectProperty(named: "deletable") public var closable: Bool
@GObjectProperty(named: "modal") public var isModal: Bool
@GObjectProperty(named: "decorated") public var isDecorated: Bool

Expand Down
24 changes: 22 additions & 2 deletions Sources/Gtk3Backend/Gtk3Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,18 @@ public final class Gtk3Backend: AppBackend {
window.title = title
}

public func setResizability(ofWindow window: Window, to resizable: Bool) {
public func setBehaviors(
ofWindow window: Window,
closable: Bool,
minimizable: Bool,
resizable: Bool
) {
// FIXME: This doesn't seem to work on macOS at least
window.closable = closable

// TODO: Figure out if there's some magic way to disable minimization
// in a framework where the minimize button usually doesn't even exist

window.resizable = resizable
}

Expand Down Expand Up @@ -207,9 +218,18 @@ public final class Gtk3Backend: AppBackend {
)
}

public func setMinimumSize(ofWindow window: Window, to minimumSize: SIMD2<Int>) {
public func setSizeLimits(
ofWindow window: Window,
minimum minimumSize: SIMD2<Int>,
maximum maximumSize: SIMD2<Int>?
) {
let child = window.child! as! CustomRootWidget
child.setMinimumSize(minimumWidth: minimumSize.x, minimumHeight: minimumSize.y)

// NB: GTK does not support setting maximum sizes for widgets. It just doesn't.
if maximumSize != nil {
logger.warning("GTK does not support setting maximum window sizes")
}
}

public func setResizeHandler(
Expand Down
24 changes: 22 additions & 2 deletions Sources/GtkBackend/GtkBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,18 @@
window.title = title
}

public func setResizability(ofWindow window: Window, to resizable: Bool) {
public func setBehaviors(
ofWindow window: Window,
closable: Bool,
minimizable: Bool,
resizable: Bool
) {
// FIXME: This doesn't seem to work on macOS at least

Check warning on line 158 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: FIXMEs should be resolved (This doesn't seem to work on m...) (todo)
window.closable = closable

// TODO: Figure out if there's some magic way to disable minimization

Check warning on line 161 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Figure out if there's some mag...) (todo)
// in a framework where the minimize button usually doesn't even exist

window.resizable = resizable
}

Expand All @@ -163,7 +174,7 @@
return 0
#else
if window.showMenuBar {
// TODO: Don't hardcode this (if possible), because some Gtk

Check warning on line 177 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Don't hardcode this (if possib...) (todo)
// themes may affect the height of the menu bar.
25
} else {
Expand All @@ -179,7 +190,7 @@
}

public func isWindowProgrammaticallyResizable(_ window: Window) -> Bool {
// TODO: Detect whether window is fullscreen

Check warning on line 193 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Detect whether window is fulls...) (todo)
return true
}

Expand All @@ -192,8 +203,17 @@
child.preemptAllocatedSize(allocatedWidth: newSize.x, allocatedHeight: newSize.y)
}

public func setMinimumSize(ofWindow window: Window, to minimumSize: SIMD2<Int>) {
public func setSizeLimits(
ofWindow window: Window,
minimum minimumSize: SIMD2<Int>,
maximum maximumSize: SIMD2<Int>?
) {
window.setMinimumSize(to: Size(width: minimumSize.x, height: minimumSize.y))

// NB: GTK does not support setting maximum sizes for widgets. It just doesn't.
if maximumSize != nil {
logger.warning("GTK does not support setting maximum window sizes")
}
}

public func setResizeHandler(
Expand Down Expand Up @@ -280,7 +300,7 @@

model.appendItem(label: label, actionName: "\(actionNamespace).\(actionName)")
case .toggle(let label, let value, let onChange):
// FIXME: Implement

Check warning on line 303 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: FIXMEs should be resolved (Implement) (todo)
logger.warning("menu toggles not implemented")
case .submenu(let submenu):
model.appendSubmenu(
Expand Down Expand Up @@ -386,14 +406,14 @@
}

public func setRootEnvironmentChangeHandler(to action: @escaping () -> Void) {
// TODO: React to theme changes

Check warning on line 409 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (React to theme changes) (todo)
}

public func computeWindowEnvironment(
window: Window,
rootEnvironment: EnvironmentValues
) -> EnvironmentValues {
// TODO: Record window scale factor in here

Check warning on line 416 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Record window scale factor in ...) (todo)
rootEnvironment
}

Expand All @@ -401,7 +421,7 @@
of window: Window,
to action: @escaping () -> Void
) {
// TODO: Notify when window scale factor changes

Check warning on line 424 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Notify when window scale facto...) (todo)
}

public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
Expand Down Expand Up @@ -1179,7 +1199,7 @@
}
},
window: window ?? windows[0]
) { result in

Check warning on line 1202 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Multiple Closures with Trailing Closure Violation: Trailing closure syntax should not be used when passing more than one closure argument (multiple_closures_with_trailing_closure)
switch result {
case .success(let urls):
handleResult(.success(urls[0]))
Expand Down Expand Up @@ -1212,7 +1232,7 @@
configure(chooser)

chooser.registerSignals()
chooser.response = { (_: NativeDialog, response: Int) -> Void in

Check warning on line 1235 in Sources/GtkBackend/GtkBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Redundant Void Return Violation: Returning Void in a function declaration is redundant (redundant_void_return)
// Release our intentional retain cycle which ironically only exists
// because of this line. The retain cycle keeps the file chooser
// around long enough for the user to respond (it gets released
Expand Down
34 changes: 28 additions & 6 deletions Sources/SwiftCrossUI/Backend/AppBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,20 @@ public protocol AppBackend: Sendable {
func createWindow(withDefaultSize defaultSize: SIMD2<Int>?) -> Window
/// Sets the title of a window.
func setTitle(ofWindow window: Window, to title: String)
/// Sets the resizability of a window. Even if resizable, the window
/// shouldn't be allowed to become smaller than its content.
func setResizability(ofWindow window: Window, to resizable: Bool)
/// Sets the behaviors of a window.
/// - Parameters:
/// - window: The window to set the behaviors on.
/// - closable: Whether the window can be closed by the user.
/// - minimizable: Whether the window can be minimized by the user.
/// - resizable: Whether the window can be resized by the user. Even if
/// resizable, the window shouldn't be allowed to become smaller than
/// its content.
func setBehaviors(
ofWindow window: Window,
closable: Bool,
minimizable: Bool,
resizable: Bool
)
/// Sets the root child of a window (replaces the previous child if any).
func setChild(ofWindow window: Window, to child: Widget)
/// Gets the size of the given window in pixels.
Expand All @@ -148,9 +159,20 @@ public protocol AppBackend: Sendable {
func isWindowProgrammaticallyResizable(_ window: Window) -> Bool
/// Sets the size of the given window in pixels.
func setSize(ofWindow window: Window, to newSize: SIMD2<Int>)
/// Sets the minimum width and height of the window. Prevents the user from making the
/// window any smaller than the given size.
func setMinimumSize(ofWindow window: Window, to minimumSize: SIMD2<Int>)
/// Sets the minimum and maximum width and height of a window.
///
/// Prevents the user from making the window any smaller or larger than the given minimum and
/// maximum sizes, respectively.
/// - Parameters:
/// - window: The window to set the size limits of.
/// - minimumSize: The minimum window size.
/// - maximumSize: maximumSize: The maximum window size. If `nil`, any existing maximum size
/// constraints should be removed.
func setSizeLimits(
ofWindow window: Window,
minimum minimumSize: SIMD2<Int>,
maximum maximumSize: SIMD2<Int>?
)
/// Sets the handler for the window's resizing events. `action` takes the proposed size
/// of the window and returns the final size for the window (which allows SwiftCrossUI
/// to implement features such as dynamic minimum window sizes based off the content's
Expand Down
Loading
Loading