Add SaveFileDialog

Add Toolbar Styles
Add Invisible Titlebar option with drag support
This commit is contained in:
Lea Anthony 2022-12-20 21:49:19 +11:00
commit 025e8d7645
No known key found for this signature in database
GPG key ID: 33DAF7BB90A58405
12 changed files with 339 additions and 38 deletions

View file

@ -140,7 +140,7 @@ func main() {
openMenu.Add("Open File").OnClick(func(ctx *application.Context) {
result, _ := app.NewOpenFileDialog().
CanChooseFiles(true).
PromptForSingleFile()
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
} else {
@ -152,7 +152,7 @@ func main() {
CanChooseFiles(true).
CanCreateDirectories(true).
ShowHiddenFiles(true).
PromptForSingleFile()
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
} else {
@ -165,7 +165,7 @@ func main() {
CanCreateDirectories(true).
ShowHiddenFiles(true).
AttachToWindow(app.GetCurrentWindow()).
PromptForSingleFile()
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
} else {
@ -177,7 +177,7 @@ func main() {
CanChooseFiles(true).
CanCreateDirectories(true).
ShowHiddenFiles(true).
PromptForMultipleFiles()
PromptForMultipleSelection()
if len(result) > 0 {
app.NewInfoDialog().SetMessage(strings.Join(result, ",")).Show()
} else {
@ -187,7 +187,7 @@ func main() {
openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) {
result, _ := app.NewOpenFileDialog().
CanChooseDirectories(true).
PromptForSingleFile()
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
} else {
@ -198,7 +198,7 @@ func main() {
result, _ := app.NewOpenFileDialog().
CanChooseDirectories(true).
CanCreateDirectories(true).
PromptForSingleFile()
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
} else {
@ -206,6 +206,39 @@ func main() {
}
})
saveMenu := menu.AddSubmenu("Save")
saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) {
result, _ := app.NewSaveFileDialog().
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
}
})
saveMenu.Add("Select File (Attach To Window)").OnClick(func(ctx *application.Context) {
result, _ := app.NewSaveFileDialog().
AttachToWindow(app.GetCurrentWindow()).
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
}
})
saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) {
result, _ := app.NewSaveFileDialog().
ShowHiddenFiles(true).
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
}
})
saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) {
result, _ := app.NewSaveFileDialog().
CanCreateDirectories(false).
PromptForSingleSelection()
if result != "" {
app.NewInfoDialog().SetMessage(result).Show()
}
})
app.SetMenu(menu)
app.NewWindow()

View file

@ -12,6 +12,18 @@ import (
func main() {
app := application.New()
// Create window
app.NewWindowWithOptions(&options.Window{
Title: "Plain Bundle",
EnableDevTools: true,
HTML: `<html><head><title>Plain Bundle</title></head><body><div class="main"><h1>Plain Bundle</h1><p>This is a plain bundle. It has no frontend code.</p></div></body></html>`,
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: &options.MacWindow{
InvisibleTitleBarHeight: 50,
Backdrop: options.MacBackdropTranslucent,
TitleBar: options.TitleBarHiddenInset,
},
})
// Create window
app.NewWindowWithOptions(&options.Window{
Title: "Plain Bundle",

View file

@ -23,7 +23,7 @@ func main() {
EnableDevTools: true,
Mac: &options.MacWindow{
Backdrop: options.MacBackdropTranslucent,
TitleBar: options.TitleBarHiddenInset,
TitleBar: options.TitleBarHiddenInsetUnified,
},
})
myWindow.On(events.Mac.WindowDidBecomeMain, func() {

View file

@ -152,7 +152,7 @@ func (a *App) NewSystemTray() *SystemTray {
}
func (a *App) Run() error {
a.impl = newPlatformApp(a.options)
a.impl = newPlatformApp(a)
a.running = true
go func() {
@ -344,3 +344,7 @@ func (a *App) NewOpenDirectoryDialog() *MessageDialog {
func (a *App) NewOpenFileDialog() *OpenFileDialog {
return newOpenFileDialog()
}
func (a *App) NewSaveFileDialog() *SaveFileDialog {
return newSaveFileDialog()
}

View file

@ -20,6 +20,28 @@ static void init(void) {
[NSApplication sharedApplication];
appDelegate = [[AppDelegate alloc] init];
[NSApp setDelegate:appDelegate];
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
NSWindow* eventWindow = [event window];
if (![eventWindow respondsToSelector:@selector(handleLeftMouseDown)]) {
return event;
}
WindowDelegate* windowDelegate = (WindowDelegate*)[eventWindow delegate];
[windowDelegate handleLeftMouseDown:event];
return event;
}];
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
NSWindow* eventWindow = [event window];
if (![eventWindow respondsToSelector:@selector(handleLeftMouseUp)]) {
return event;
}
WindowDelegate* windowDelegate = (WindowDelegate*)[eventWindow delegate];
[windowDelegate handleLeftMouseUp:eventWindow];
return event;
}];
}
static void setActivationPolicy(int policy) {
@ -79,12 +101,14 @@ import "C"
import (
"unsafe"
"github.com/wailsapp/wails/exp/pkg/events"
"github.com/wailsapp/wails/exp/pkg/options"
)
type macosApp struct {
options *options.Application
applicationMenu unsafe.Pointer
parent *App
}
func (m *macosApp) setIcon(icon []byte) {
@ -107,12 +131,19 @@ func (m *macosApp) setApplicationMenu(menu *Menu) {
menu = defaultApplicationMenu()
}
menu.Update()
// Convert impl to macosMenu object
m.applicationMenu = (menu.impl).(*macosMenu).nsMenu
C.setApplicationMenu(m.applicationMenu)
}
func (m *macosApp) run() error {
m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() {
if m.parent.options != nil && m.parent.options.Mac != nil {
C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy))
}
C.activateIgnoringOtherApps()
})
C.run()
return nil
}
@ -121,15 +152,14 @@ func (m *macosApp) destroy() {
C.destroyApp()
}
func newPlatformApp(appOptions *options.Application) *macosApp {
func newPlatformApp(app *App) *macosApp {
appOptions := app.options
if appOptions == nil {
appOptions = options.ApplicationDefaults
}
C.init()
C.setActivationPolicy(C.int(appOptions.Mac.ActivationPolicy))
C.activateIgnoringOtherApps()
return &macosApp{
options: appOptions,
parent: app,
}
}

View file

@ -7,6 +7,7 @@ import (
type DialogType int
// TODO: Make this a map and clear it when the dialog is closed
var dialogID uint
var dialogIDLock sync.RWMutex
@ -18,6 +19,7 @@ func getDialogID() uint {
}
var openFileResponses = make(map[uint]chan string)
var saveFileResponses = make(map[uint]chan string)
const (
InfoDialog DialogType = iota
@ -154,7 +156,7 @@ func (d *OpenFileDialog) AttachToWindow(window *Window) *OpenFileDialog {
return d
}
func (d *OpenFileDialog) PromptForSingleFile() (string, error) {
func (d *OpenFileDialog) PromptForSingleSelection() (string, error) {
d.allowsMultipleSelection = false
if d.impl == nil {
d.impl = newOpenFileDialogImpl(d)
@ -168,7 +170,7 @@ func (d *OpenFileDialog) PromptForSingleFile() (string, error) {
return result, err
}
func (d *OpenFileDialog) PromptForMultipleFiles() ([]string, error) {
func (d *OpenFileDialog) PromptForMultipleSelection() ([]string, error) {
d.allowsMultipleSelection = true
if d.impl == nil {
d.impl = newOpenFileDialogImpl(d)
@ -181,6 +183,48 @@ func newOpenFileDialog() *OpenFileDialog {
id: getDialogID(),
canChooseDirectories: false,
canChooseFiles: true,
canCreateDirectories: false,
canCreateDirectories: true,
}
}
func newSaveFileDialog() *SaveFileDialog {
return &SaveFileDialog{
id: getDialogID(),
canCreateDirectories: true,
}
}
type SaveFileDialog struct {
id uint
canCreateDirectories bool
showHiddenFiles bool
window *Window
impl saveFileDialogImpl
}
type saveFileDialogImpl interface {
show() (string, error)
}
func (d *SaveFileDialog) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialog {
d.canCreateDirectories = canCreateDirectories
return d
}
func (d *SaveFileDialog) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialog {
d.showHiddenFiles = showHiddenFiles
return d
}
func (d *SaveFileDialog) AttachToWindow(window *Window) *SaveFileDialog {
d.window = window
return d
}
func (d *SaveFileDialog) PromptForSingleSelection() (string, error) {
if d.impl == nil {
d.impl = newSaveFileDialogImpl(d)
}
return d.impl.show()
}

View file

@ -10,6 +10,7 @@ package application
extern void openFileDialogCallback(uint id, char* path);
extern void openFileDialogCallbackEnd(uint id);
extern void saveFileDialogCallback(uint id, char* path);
static void showAboutBox(char* title, char *message, void *icon, int length) {
@ -146,6 +147,52 @@ static void showOpenFileDialog(unsigned int dialogID, bool canChooseFiles, bool
});
}
static void showSaveFileDialog(unsigned int dialogID, bool canCreateDirectories, bool showHiddenFiles, void *window) {
// run on main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setCanCreateDirectories:canCreateDirectories];
[panel setShowsHiddenFiles:showHiddenFiles];
//if (title != NULL) {
// [panel setTitle:[NSString stringWithUTF8String:title]];
// free(title);
//}
//if (defaultFilename != NULL) {
// [panel setNameFieldStringValue:[NSString stringWithUTF8String:defaultFilename]];
// free(defaultFilename);
//}
//
//if (defaultDirectory != NULL) {
// [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:defaultDirectory]]];
// free(defaultDirectory);
//}
if (window != NULL) {
[panel beginSheetModalForWindow:(__bridge NSWindow *)window completionHandler:^(NSInteger result) {
const char *path = NULL;
if (result == NSModalResponseOK) {
NSURL *url = [panel URL];
const char *path = [[url path] UTF8String];
}
saveFileDialogCallback(dialogID, (char *)path);
}];
} else {
[panel beginWithCompletionHandler:^(NSInteger result) {
const char *path = NULL;
if (result == NSModalResponseOK) {
NSURL *url = [panel URL];
const char *path = [[url path] UTF8String];
}
saveFileDialogCallback(dialogID, (char *)path);
}];
}
});
}
*/
import "C"
import (
@ -296,3 +343,40 @@ func openFileDialogCallbackEnd(id C.uint) {
panic("No channel found for open file dialog")
}
}
type macosSaveFileDialog struct {
dialog *SaveFileDialog
}
func newSaveFileDialogImpl(d *SaveFileDialog) *macosSaveFileDialog {
return &macosSaveFileDialog{
dialog: d,
}
}
func (m *macosSaveFileDialog) show() (string, error) {
saveFileResponses[dialogID] = make(chan string)
nsWindow := unsafe.Pointer(nil)
if m.dialog.window != nil {
// get NSWindow from window
nsWindow = m.dialog.window.impl.(*macosWindow).nsWindow
}
C.showSaveFileDialog(C.uint(m.dialog.id),
C.bool(m.dialog.canCreateDirectories),
C.bool(m.dialog.showHiddenFiles),
nsWindow)
return <-saveFileResponses[m.dialog.id], nil
}
//export saveFileDialogCallback
func saveFileDialogCallback(id C.uint, path *C.char) {
// Covert the path to a string
filePath := C.GoString(path)
// put response on channel
channel, ok := saveFileResponses[uint(id)]
if ok {
channel <- filePath
} else {
panic("No channel found for save file dialog")
}
}

View file

@ -56,6 +56,15 @@ void* windowNew(unsigned int id, int width, int height) {
return window;
}
// setInvisibleTitleBarHeight sets the invisible title bar height
void setInvisibleTitleBarHeight(void* window, unsigned int height) {
NSWindow* nsWindow = (NSWindow*)window;
// Get delegate
WindowDelegate* delegate = (WindowDelegate*)[nsWindow delegate];
// Set height
delegate.invisibleTitleBarHeight = height;
}
//// Make window toggle frameless
//void windowSetFrameless(void* window, bool frameless) {
// NSWindow* nsWindow = (NSWindow*)window;
@ -81,6 +90,18 @@ void windowSetTransparent(void* nsWindow) {
});
}
void windowSetInvisibleTitleBar(void* nsWindow, unsigned int height) {
// On main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSWindow* window = (NSWindow*)nsWindow;
// Get delegate
WindowDelegate* delegate = (WindowDelegate*)[window delegate];
// Set height
delegate.invisibleTitleBarHeight = height;
});
}
// Set the title of the NSWindow
void windowSetTitle(void* nsWindow, char* title) {
// Set window title on main thread
@ -407,7 +428,7 @@ void windowSetHideTitle(void* nsWindow, bool hideTitle) {
}
// Set Window use toolbar
void windowSetUseToolbar(void* nsWindow, bool useToolbar) {
void windowSetUseToolbar(void* nsWindow, bool useToolbar, int toolbarStyle) {
// Set window use toolbar on main thread
dispatch_async(dispatch_get_main_queue(), ^{
// get main window
@ -416,12 +437,35 @@ void windowSetUseToolbar(void* nsWindow, bool useToolbar) {
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"];
[toolbar autorelease];
[window setToolbar:toolbar];
// If macos 11 or higher, set toolbar style
if (@available(macOS 11.0, *)) {
[window setToolbarStyle:toolbarStyle];
}
} else {
[window setToolbar:nil];
}
});
}
// Set window toolbar style
void windowSetToolbarStyle(void* nsWindow, int style) {
// use @available to check if the function is available
// if not, return
if (@available(macOS 11.0, *)) {
// Set window toolbar style on main thread
dispatch_async(dispatch_get_main_queue(), ^{
// get main window
NSWindow* window = (NSWindow*)nsWindow;
// get toolbar
NSToolbar* toolbar = [window toolbar];
// set toolbar style
[toolbar setShowsBaselineSeparator:style];
});
}
}
// Set Hide Toolbar Separator
void windowSetHideToolbarSeparator(void* nsWindow, bool hideSeparator) {
// Set window hide toolbar separator on main thread
@ -808,7 +852,9 @@ func (w *macosWindow) run() {
C.windowSetHideTitleBar(w.nsWindow, C.bool(titleBarOptions.Hide))
C.windowSetHideTitle(w.nsWindow, C.bool(titleBarOptions.HideTitle))
C.windowSetFullSizeContent(w.nsWindow, C.bool(titleBarOptions.FullSizeContent))
C.windowSetUseToolbar(w.nsWindow, C.bool(titleBarOptions.UseToolbar))
if titleBarOptions.UseToolbar {
C.windowSetUseToolbar(w.nsWindow, C.bool(titleBarOptions.UseToolbar), C.int(titleBarOptions.ToolbarStyle))
}
C.windowSetHideToolbarSeparator(w.nsWindow, C.bool(titleBarOptions.HideToolbarSeparator))
}
@ -816,15 +862,18 @@ func (w *macosWindow) run() {
C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance)))
}
switch w.parent.options.StartState {
case options.WindowStateMaximised:
w.setMaximised()
case options.WindowStateMinimised:
w.setMinimised()
case options.WindowStateFullscreen:
w.setFullscreen()
if macOptions.InvisibleTitleBarHeight != 0 {
C.windowSetInvisibleTitleBar(w.nsWindow, C.uint(macOptions.InvisibleTitleBarHeight))
}
}
switch w.parent.options.StartState {
case options.WindowStateMaximised:
w.setMaximised()
case options.WindowStateMinimised:
w.setMinimised()
case options.WindowStateFullscreen:
w.setFullscreen()
}
C.windowCenter(w.nsWindow)
@ -832,7 +881,7 @@ func (w *macosWindow) run() {
if w.parent.options.URL != "" {
w.navigateToURL(w.parent.options.URL)
}
// Ee need to wait for the HTML to load before we can execute the javascript
// We need to wait for the HTML to load before we can execute the javascript
w.parent.On(events.Mac.WebViewDidFinishNavigation, func() {
if w.parent.options.JS != "" {
w.execJS(w.parent.options.JS)

View file

@ -11,6 +11,11 @@
@property bool hideOnClose;
@property (retain) WKWebView* webView;
@property unsigned int windowId;
@property (retain) NSEvent* leftMouseEvent;
@property unsigned int invisibleTitleBarHeight;
- (void)handleLeftMouseUp:(NSWindow *)window;
- (void)handleLeftMouseDown:(NSEvent*)event;
@end

View file

@ -37,10 +37,26 @@ extern void processMessage(unsigned int, const char*);
processMessage(self.windowId, _m);
}
- (void) mouseDown:(NSEvent*)someEvent {
NSLog(@"MOUSE DOWN!!!");
- (void)handleLeftMouseDown:(NSEvent *)event {
self.leftMouseEvent = event;
NSWindow *window = [event window];
WindowDelegate* delegate = (WindowDelegate*)[window delegate];
if( self.invisibleTitleBarHeight > 0 ) {
NSPoint location = [event locationInWindow];
NSRect frame = [window frame];
if( location.y > frame.size.height - self.invisibleTitleBarHeight ) {
[window performWindowDragWithEvent:event];
return;
}
}
}
- (void)handleLeftMouseUp:(NSWindow *)window {
self.leftMouseEvent = nil;
}
// GENERATED EVENTS START
- (void)windowDidBecomeKey:(NSNotification *)notification {
processWindowEvent(self.windowId, EventWindowDidBecomeKey);

View file

@ -24,11 +24,6 @@ void showDevTools(void *window) {
WindowDelegate* delegate = (WindowDelegate*)[(NSWindow*)window delegate];
dispatch_async(dispatch_get_main_queue(), ^{
[delegate.webView._inspector show];
//dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
//dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// // Detach must be deferred a little bit and is ignored directly after a show.
// [delegate.webView._inspector detach];
//});
});
}

View file

@ -22,11 +22,27 @@ const (
MacBackdropTranslucent
)
type MacToolbarStyle int
const (
// MacToolbarStyleAutomatic - The default value. The style will be determined by the window's given configuration
MacToolbarStyleAutomatic MacToolbarStyle = iota
// MacToolbarStyleExpanded - The toolbar will appear below the window title
MacToolbarStyleExpanded
// MacToolbarStylePreference - The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible
MacToolbarStylePreference
// MacToolbarStyleUnified - The window title will appear inline with the toolbar when visible
MacToolbarStyleUnified
// MacToolbarStyleUnifiedCompact - Same as MacToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window
MacToolbarStyleUnifiedCompact
)
// MacWindow contains macOS specific options
type MacWindow struct {
Backdrop MacBackdrop
TitleBar *TitleBar
Appearance MacAppearanceType
Backdrop MacBackdrop
TitleBar *TitleBar
Appearance MacAppearanceType
InvisibleTitleBarHeight int
}
// TitleBar contains options for the Mac titlebar
@ -37,9 +53,10 @@ type TitleBar struct {
FullSizeContent bool
UseToolbar bool
HideToolbarSeparator bool
ToolbarStyle MacToolbarStyle
}
// TitleBarDefault results in the default Mac Titlebar
// TitleBarDefault results in the default Mac TitleBar
var TitleBarDefault = &TitleBar{
AppearsTransparent: false,
Hide: false,
@ -74,6 +91,18 @@ var TitleBarHiddenInset = &TitleBar{
HideToolbarSeparator: true,
}
// TitleBarHiddenInsetUnified results in a hidden title bar with an alternative look where
// the traffic light buttons are even more inset from the window edge.
var TitleBarHiddenInsetUnified = &TitleBar{
AppearsTransparent: true,
Hide: false,
HideTitle: true,
FullSizeContent: true,
UseToolbar: true,
HideToolbarSeparator: true,
ToolbarStyle: MacToolbarStyleUnified,
}
// MacAppearanceType is a type of Appearance for Cocoa windows
type MacAppearanceType string