Skip to content
Draft
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
4 changes: 3 additions & 1 deletion Platform/Shared/VMNavigationListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,10 @@ private struct VMListModifier: ViewModifier {
#endif
#if !os(visionOS) && !WITH_REMOTE
ToolbarItem(placement: .navigationBarTrailing) {
Button("Settings") {
Button {
settingsPresented.toggle()
} label: {
Label("Settings", systemImage: "gear")
}
}
#endif
Expand Down
295 changes: 194 additions & 101 deletions Platform/Shared/VMToolbarModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,116 +46,209 @@ struct VMToolbarModifier: ViewModifier {
#endif

func body(content: Content) -> some View {
toolbar(content)
.modifier(VMShareItemModifier(isPresented: $showSharePopup, shareItem: shareItem))
.modifier(VMConfirmActionModifier(confirmAction: $confirmAction) { action in
if case .confirmMoveVM(let vm) = action {
shareItem = .utmMove(vm)
showSharePopup.toggle()
}
})
}
}

// MARK: - Layouts

private extension VMToolbarModifier {
/// Toolbar content cannot branch on availability before iOS 16, so the view does.
@ViewBuilder
func toolbar(_ content: Content) -> some View {
#if os(iOS)
if #available(iOS 27, *) {
content.toolbar {
prioritizedToolbarContent
}
} else {
content.toolbar {
groupedToolbarContent
}
}
#else
content.toolbar {
#if os(visionOS)
UTMPreferenceButtonToolbarContent()
#endif
ToolbarItemGroup(placement: buttonPlacement) {
#if !WITH_REMOTE // FIXME: implement remote feature
if vm.isShortcut {
DestructiveButton {
confirmAction = .confirmDeleteVM(vm: vm)
} label: {
Label("Remove", systemImage: "trash")
.labelStyle(.iconOnly)
}.help("Remove selected shortcut")
.disabled(!vm.isModifyAllowed)
.padding(.leading, padding)
} else {
DestructiveButton {
confirmAction = .confirmDeleteVM(vm: vm)
} label: {
Label("Delete", systemImage: "trash")
.labelStyle(.iconOnly)
}.help("Delete selected VM")
.disabled(!vm.isModifyAllowed)
.padding(.leading, padding)
}
#if !os(macOS)
if bottom {
Spacer()
}
#endif
Button {
confirmAction = .confirmCloneVM(vm: vm)
} label: {
Label("Clone", systemImage: "doc.on.doc")
.labelStyle(.iconOnly)
}.help("Clone selected VM")
groupedToolbarContent
}
#endif
}

/// One group with manual spacing, which is laid out as a single unit.
var groupedToolbarContent: some ToolbarContent {
ToolbarItemGroup(placement: buttonPlacement) {
#if !WITH_REMOTE // FIXME: implement remote feature
deleteButton
.padding(.leading, padding)
#if !os(macOS)
if bottom {
Spacer()
}
#endif
#if os(macOS)
if !vm.isShortcut {
Button {
confirmAction = .confirmMoveVM(vm: vm)
} label: {
Label("Move", systemImage: "arrow.down.doc")
.labelStyle(.iconOnly)
}.help("Move selected VM")
.disabled(!vm.isModifyAllowed)
.padding(.leading, padding)
}
#endif
Button {
shareItem = .utmCopy(vm)
showSharePopup.toggle()
} label: {
Label("Share", systemImage: "square.and.arrow.up")
.labelStyle(.iconOnly)
}.help("Share selected VM")
groupSpacer
cloneButton
.padding(.leading, padding)
#if !os(macOS)
if bottom {
Spacer()
}
#endif
#endif
if vm.hasSuspendState || !vm.isStopped {
Button {
confirmAction = .confirmStopVM(vm: vm)
} label: {
Label("Stop", systemImage: "stop")
.labelStyle(.iconOnly)
}.help("Stop selected VM")
.padding(.leading, padding)
} else {
Button {
data.run(vm: data.selectedVM!)
} label: {
Label("Run", systemImage: "play")
.labelStyle(.iconOnly)
}.help("Run selected VM")
groupSpacer
#if os(macOS)
if !vm.isShortcut {
moveButton
.padding(.leading, padding)
}
#if !WITH_REMOTE // FIXME: implement remote feature
#if !os(macOS)
if bottom {
Spacer()
}
#endif
Button {
data.close(vm: vm) // close window
data.edit(vm: vm)
} label: {
Label("Edit", systemImage: "slider.horizontal.3")
.labelStyle(.iconOnly)
}.help("Edit selected VM")
.disabled(vm.hasSuspendState || !vm.isModifyAllowed)
.padding(.leading, padding)
#endif
}
#endif
shareButton
.padding(.leading, padding)
groupSpacer
#endif
runStopButton
.padding(.leading, padding)
#if !WITH_REMOTE // FIXME: implement remote feature
groupSpacer
editButton
.padding(.leading, padding)
#endif
}
}

@ViewBuilder
var groupSpacer: some View {
#if !os(macOS)
if bottom {
Spacer()
}
#endif
}

#if os(iOS)
/// Separate items so that a bar too short for all of them drops the least important ones first.
///
/// A vertical bar on the closed iPhone Duo overflows from the bottom, where Run and Stop sit.
@available(iOS 27, *)
@ToolbarContentBuilder
var prioritizedToolbarContent: some ToolbarContent {
#if !WITH_REMOTE // FIXME: implement remote feature
ToolbarItem(placement: buttonPlacement) {
deleteButton
}
if bottom {
ToolbarSpacer(.flexible, placement: buttonPlacement)
}
ToolbarItem(placement: buttonPlacement) {
cloneButton
}.visibilityPriority(.low)
if bottom {
ToolbarSpacer(.flexible, placement: buttonPlacement)
}
ToolbarItem(placement: buttonPlacement) {
shareButton
}
if bottom {
ToolbarSpacer(.flexible, placement: buttonPlacement)
}
#endif
ToolbarItem(placement: buttonPlacement) {
runStopButton
}.visibilityPriority(.high)
#if !WITH_REMOTE // FIXME: implement remote feature
if bottom {
ToolbarSpacer(.flexible, placement: buttonPlacement)
}
ToolbarItem(placement: buttonPlacement) {
editButton
}
#endif
}
#endif
}

// MARK: - Buttons

private extension VMToolbarModifier {
#if !WITH_REMOTE
@ViewBuilder
var deleteButton: some View {
if vm.isShortcut {
DestructiveButton {
confirmAction = .confirmDeleteVM(vm: vm)
} label: {
Label("Remove", systemImage: "trash")
.labelStyle(.iconOnly)
}.help("Remove selected shortcut")
.disabled(!vm.isModifyAllowed)
} else {
DestructiveButton {
confirmAction = .confirmDeleteVM(vm: vm)
} label: {
Label("Delete", systemImage: "trash")
.labelStyle(.iconOnly)
}.help("Delete selected VM")
.disabled(!vm.isModifyAllowed)
}
}

var cloneButton: some View {
Button {
confirmAction = .confirmCloneVM(vm: vm)
} label: {
Label("Clone", systemImage: "doc.on.doc")
.labelStyle(.iconOnly)
}.help("Clone selected VM")
}

#if os(macOS)
var moveButton: some View {
Button {
confirmAction = .confirmMoveVM(vm: vm)
} label: {
Label("Move", systemImage: "arrow.down.doc")
.labelStyle(.iconOnly)
}.help("Move selected VM")
.disabled(!vm.isModifyAllowed)
}
#endif

var shareButton: some View {
Button {
shareItem = .utmCopy(vm)
showSharePopup.toggle()
} label: {
Label("Share", systemImage: "square.and.arrow.up")
.labelStyle(.iconOnly)
}.help("Share selected VM")
}

var editButton: some View {
Button {
data.close(vm: vm) // close window
data.edit(vm: vm)
} label: {
Label("Edit", systemImage: "slider.horizontal.3")
.labelStyle(.iconOnly)
}.help("Edit selected VM")
.disabled(vm.hasSuspendState || !vm.isModifyAllowed)
}
#endif

@ViewBuilder
var runStopButton: some View {
if vm.hasSuspendState || !vm.isStopped {
Button {
confirmAction = .confirmStopVM(vm: vm)
} label: {
Label("Stop", systemImage: "stop")
.labelStyle(.iconOnly)
}.help("Stop selected VM")
} else {
Button {
data.run(vm: data.selectedVM!)
} label: {
Label("Run", systemImage: "play")
.labelStyle(.iconOnly)
}.help("Run selected VM")
}
.modifier(VMShareItemModifier(isPresented: $showSharePopup, shareItem: shareItem))
.modifier(VMConfirmActionModifier(confirmAction: $confirmAction) { action in
if case .confirmMoveVM(let vm) = action {
shareItem = .utmMove(vm)
showSharePopup.toggle()
}
})
}
}

Expand Down
3 changes: 3 additions & 0 deletions Platform/iOS/Display/VMCursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ NS_ASSUME_NONNULL_BEGIN

@property (nonatomic, readwrite) CGRect bounds;

/// The drag speed setting as a factor applied to relative motion.
@property (nonatomic, readonly) CGFloat cursorSpeedMultiplier;

- (id)initWithVMViewController:(VMDisplayMetalViewController *)controller;
- (void)startMovement:(CGPoint)startPoint;
- (void)updateMovement:(CGPoint)point;
Expand Down
6 changes: 0 additions & 6 deletions Platform/iOS/Display/VMCursor.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@
#import "VMDisplayMetalViewController+Touch.h"
#import "CSDisplay.h"

@interface VMCursor ()

@property (nonatomic, readonly) CGFloat cursorSpeedMultiplier;

@end

@implementation VMCursor {
CGPoint _start;
CGPoint _lastCenter;
Expand Down
2 changes: 2 additions & 0 deletions Platform/iOS/Display/VMDisplayMetalViewController+Keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN

- (void)keyboardDonePressed:(UIButton *)sender;
- (void)keyboardPastePressed:(UIButton *)sender;
- (void)keyboardTouchpadPressed:(UIButton *)sender;
- (void)touchpadKeyboardPressed:(UIButton *)sender;
- (void)customKeyTouchDown:(VMKeyboardButton *)sender;
- (void)customKeyTouchUp:(VMKeyboardButton *)sender;

Expand Down
8 changes: 8 additions & 0 deletions Platform/iOS/Display/VMDisplayMetalViewController+Keyboard.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ - (void)keyboardPastePressed:(UIButton *)sender {
}
}

- (void)keyboardTouchpadPressed:(UIButton *)sender {
[self.delegate displayDidRequestInputDeck:VMInputDeckTouchpad];
}

- (void)touchpadKeyboardPressed:(UIButton *)sender {
[self.delegate displayDidRequestInputDeck:VMInputDeckKeyboard];
}

- (void)resetModifierToggles {
for (VMKeyboardButton *button in self.inputAccessoryView.modifierButtons) {
if (button.isToggled) {
Expand Down
Loading
Loading