package glfw import ( "context" _ "image/png" // for the icon "math" "runtime" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/internal/app" "fyne.io/fyne/v2/internal/cache" "fyne.io/fyne/v2/internal/driver" "fyne.io/fyne/v2/internal/driver/common" "fyne.io/fyne/v2/internal/scale" ) const ( doubleClickDelay = 300 // ms (maximum interval between clicks for double click detection) dragMoveThreshold = 2 // how far can we move before it is a drag windowIconSize = 256 ) func (w *window) Title() string { return w.title } func (w *window) SetTitle(title string) { w.title = title w.runOnMainWhenCreated(func() { w.view().SetTitle(title) }) } func (w *window) FullScreen() bool { return w.fullScreen } // minSizeOnScreen gets the padded minimum size of a window content in screen pixels func (w *window) minSizeOnScreen() (int, int) { // get minimum size of content inside the window return w.screenSize(w.canvas.MinSize()) } // screenSize computes the actual output size of the given content size in screen pixels func (w *window) screenSize(canvasSize fyne.Size) (int, int) { return scale.ToScreenCoordinate(w.canvas, canvasSize.Width), scale.ToScreenCoordinate(w.canvas, canvasSize.Height) } func (w *window) Resize(size fyne.Size) { // we cannot perform this until window is prepared as we don't know it's scale! bigEnough := size.Max(w.canvas.canvasSize(w.canvas.Content().MinSize())) w.runOnMainWhenCreated(func() { w.viewLock.Lock() width, height := scale.ToScreenCoordinate(w.canvas, bigEnough.Width), scale.ToScreenCoordinate(w.canvas, bigEnough.Height) if w.fixedSize || !w.visible { // fixed size ignores future `resized` and if not visible we may not get the event w.shouldWidth, w.shouldHeight = width, height w.width, w.height = width, height } w.viewLock.Unlock() w.requestedWidth, w.requestedHeight = width, height w.view().SetSize(width, height) }) } func (w *window) FixedSize() bool { return w.fixedSize } func (w *window) SetFixedSize(fixed bool) { w.fixedSize = fixed if w.view() != nil { w.runOnMainWhenCreated(w.fitContent) } } func (w *window) Padded() bool { return w.canvas.padded } func (w *window) SetPadded(padded bool) { w.canvas.SetPadded(padded) w.runOnMainWhenCreated(w.fitContent) } func (w *window) Icon() fyne.Resource { if w.icon == nil { return fyne.CurrentApp().Icon() } return w.icon } func (w *window) MainMenu() *fyne.MainMenu { return w.mainmenu } func (w *window) SetMainMenu(menu *fyne.MainMenu) { w.mainmenu = menu w.runOnMainWhenCreated(func() { w.canvas.buildMenu(w, menu) }) } func (w *window) SetOnClosed(closed func()) { w.onClosed = closed } func (w *window) SetCloseIntercept(callback func()) { w.onCloseIntercepted = callback } func (w *window) calculatedScale() float32 { return calculateScale(userScale(), fyne.CurrentDevice().SystemScaleForWindow(w), w.detectScale()) } func (w *window) detectTextureScale() float32 { view := w.view() winWidth, _ := view.GetSize() texWidth, _ := view.GetFramebufferSize() return float32(texWidth) / float32(winWidth) } func (w *window) Show() { go w.doShow() } func (w *window) doShow() { if w.view() != nil { w.doShowAgain() return } run.L.Lock() for !run.flag { run.Wait() } run.L.Unlock() w.createLock.Do(w.create) if w.view() == nil { return } runOnMain(func() { w.viewLock.Lock() w.visible = true w.viewLock.Unlock() view := w.view() view.SetTitle(w.title) if w.centered { w.doCenterOnScreen() // lastly center if that was requested } view.Show() // save coordinates w.xpos, w.ypos = view.GetPos() if w.fullScreen { // this does not work if called before viewport.Show() go func() { time.Sleep(time.Millisecond * 100) w.SetFullScreen(true) }() } }) // show top canvas element if content := w.canvas.Content(); content != nil { content.Show() runOnDraw(w, func() { w.driver.repaintWindow(w) }) } } func (w *window) Hide() { runOnMain(func() { w.viewLock.Lock() if w.closing || w.viewport == nil { w.viewLock.Unlock() return } w.visible = false v := w.viewport w.viewLock.Unlock() v.Hide() // hide top canvas element if content := w.canvas.Content(); content != nil { content.Hide() } }) } func (w *window) Close() { if w.isClosing() { return } // trigger callbacks - early so window still exists if w.onClosed != nil { w.QueueEvent(w.onClosed) } // set w.closing flag inside draw thread to ensure we can free textures runOnDraw(w, func() { w.viewLock.Lock() w.closing = true w.viewLock.Unlock() w.viewport.SetShouldClose(true) cache.RangeTexturesFor(w.canvas, w.canvas.Painter().Free) }) w.canvas.WalkTrees(nil, func(node *common.RenderCacheNode, _ fyne.Position) { if wid, ok := node.Obj().(fyne.Widget); ok { cache.DestroyRenderer(wid) } }) } func (w *window) ShowAndRun() { w.Show() w.driver.Run() } // Clipboard returns the system clipboard func (w *window) Clipboard() fyne.Clipboard { if w.view() == nil { return nil } if w.clipboard == nil { w.clipboard = &clipboard{window: w.viewport} } return w.clipboard } func (w *window) Content() fyne.CanvasObject { return w.canvas.Content() } func (w *window) SetContent(content fyne.CanvasObject) { w.viewLock.RLock() visible := w.visible w.viewLock.RUnlock() // hide old canvas element if visible && w.canvas.Content() != nil { w.canvas.Content().Hide() } w.canvas.SetContent(content) // show new canvas element if content != nil { content.Show() } w.RescaleContext() } func (w *window) Canvas() fyne.Canvas { return w.canvas } func (w *window) processClosed() { if w.onCloseIntercepted != nil { w.QueueEvent(w.onCloseIntercepted) return } go w.Close() // unsure which thread this comes from, so don't block } // destroy this window and, if it's the last window quit the app func (w *window) destroy(d *gLDriver) { w.DestroyEventQueue() cache.CleanCanvas(w.canvas) if w.master { d.Quit() } else if runtime.GOOS == "darwin" { go d.focusPreviousWindow() } } func (w *window) processMoved(x, y int) { if !w.fullScreen { // don't save the move to top left when changing to fullscreen // save coordinates w.xpos, w.ypos = x, y } if w.canvas.detectedScale == w.detectScale() { return } w.canvas.detectedScale = w.detectScale() go w.canvas.reloadScale() } func (w *window) processResized(width, height int) { canvasSize := w.computeCanvasSize(width, height) if !w.fullScreen { w.width = scale.ToScreenCoordinate(w.canvas, canvasSize.Width) w.height = scale.ToScreenCoordinate(w.canvas, canvasSize.Height) } if !w.visible { // don't redraw if hidden w.canvas.Resize(canvasSize) return } if w.fixedSize { w.canvas.Resize(canvasSize) w.fitContent() return } w.platformResize(canvasSize) } func (w *window) processFrameSized(width, height int) { if width == 0 || height == 0 || runtime.GOOS != "darwin" { return } winWidth, _ := w.view().GetSize() newTexScale := float32(width) / float32(winWidth) // This will be > 1.0 on a HiDPI screen w.canvas.RLock() texScale := w.canvas.texScale w.canvas.RUnlock() if texScale != newTexScale { w.canvas.Lock() w.canvas.texScale = newTexScale w.canvas.Unlock() w.canvas.Refresh(w.canvas.Content()) // reset graphics to apply texture scale } } func (w *window) processRefresh() { refreshWindow(w) } func (w *window) findObjectAtPositionMatching(canvas *glCanvas, mouse fyne.Position, matches func(object fyne.CanvasObject) bool) (fyne.CanvasObject, fyne.Position, int) { return driver.FindObjectAtPositionMatching(mouse, matches, canvas.Overlays().Top(), canvas.menu, canvas.Content()) } func (w *window) processMouseMoved(xpos float64, ypos float64) { w.mouseLock.Lock() previousPos := w.mousePos w.mousePos = fyne.NewPos(scale.ToFyneCoordinate(w.canvas, int(xpos)), scale.ToFyneCoordinate(w.canvas, int(ypos))) mousePos := w.mousePos mouseButton := w.mouseButton mouseDragPos := w.mouseDragPos mouseOver := w.mouseOver w.mouseLock.Unlock() cursor := desktop.Cursor(desktop.DefaultCursor) obj, pos, _ := w.findObjectAtPositionMatching(w.canvas, mousePos, func(object fyne.CanvasObject) bool { if cursorable, ok := object.(desktop.Cursorable); ok { cursor = cursorable.Cursor() } _, hover := object.(desktop.Hoverable) return hover }) if w.cursor != cursor { // cursor has changed, store new cursor and apply change via glfw rawCursor, isCustomCursor := fyneToNativeCursor(cursor) w.cursor = cursor if rawCursor == nil { w.view().SetInputMode(CursorMode, CursorHidden) } else { w.view().SetInputMode(CursorMode, CursorNormal) w.SetCursor(rawCursor) } w.setCustomCursor(rawCursor, isCustomCursor) } if w.mouseButton != 0 && w.mouseButton != desktop.MouseButtonSecondary && !w.mouseDragStarted { obj, pos, _ := w.findObjectAtPositionMatching(w.canvas, previousPos, func(object fyne.CanvasObject) bool { _, ok := object.(fyne.Draggable) return ok }) deltaX := mousePos.X - mouseDragPos.X deltaY := mousePos.Y - mouseDragPos.Y overThreshold := math.Abs(float64(deltaX)) >= dragMoveThreshold || math.Abs(float64(deltaY)) >= dragMoveThreshold if wid, ok := obj.(fyne.Draggable); ok && overThreshold { w.mouseLock.Lock() w.mouseDragged = wid w.mouseDraggedOffset = previousPos.Subtract(pos) w.mouseDraggedObjStart = obj.Position() w.mouseDragStarted = true w.mouseLock.Unlock() } } w.mouseLock.RLock() isObjDragged := w.objIsDragged(obj) isMouseOverDragged := w.objIsDragged(mouseOver) w.mouseLock.RUnlock() if obj != nil && !isObjDragged { ev := &desktop.MouseEvent{Button: mouseButton} ev.AbsolutePosition = mousePos ev.Position = pos if hovered, ok := obj.(desktop.Hoverable); ok { if hovered == mouseOver { w.QueueEvent(func() { hovered.MouseMoved(ev) }) } else { w.mouseOut() w.mouseIn(hovered, ev) } } else if mouseOver != nil { isChild := false driver.WalkCompleteObjectTree(mouseOver.(fyne.CanvasObject), func(co fyne.CanvasObject, p1, p2 fyne.Position, s fyne.Size) bool { if co == obj { isChild = true return true } return false }, nil) if !isChild { w.mouseOut() } } } else if mouseOver != nil && !isMouseOverDragged { w.mouseOut() } w.mouseLock.RLock() mouseButton = w.mouseButton mouseDragged := w.mouseDragged mouseDraggedObjStart := w.mouseDraggedObjStart mouseDraggedOffset := w.mouseDraggedOffset mouseDragPos = w.mouseDragPos w.mouseLock.RUnlock() if mouseDragged != nil && mouseButton != desktop.MouseButtonSecondary { if w.mouseButton > 0 { draggedObjDelta := mouseDraggedObjStart.Subtract(mouseDragged.(fyne.CanvasObject).Position()) ev := &fyne.DragEvent{} ev.AbsolutePosition = mousePos ev.Position = mousePos.Subtract(mouseDraggedOffset).Add(draggedObjDelta) ev.Dragged = fyne.NewDelta(mousePos.X-mouseDragPos.X, mousePos.Y-mouseDragPos.Y) wd := mouseDragged w.QueueEvent(func() { wd.Dragged(ev) }) } w.mouseLock.Lock() w.mouseDragStarted = true w.mouseDragPos = mousePos w.mouseLock.Unlock() } } func (w *window) objIsDragged(obj interface{}) bool { if w.mouseDragged != nil && obj != nil { draggedObj, _ := obj.(fyne.Draggable) return draggedObj == w.mouseDragged } return false } func (w *window) mouseIn(obj desktop.Hoverable, ev *desktop.MouseEvent) { w.QueueEvent(func() { if obj != nil { obj.MouseIn(ev) } w.mouseLock.Lock() w.mouseOver = obj w.mouseLock.Unlock() }) } func (w *window) mouseOut() { w.QueueEvent(func() { w.mouseLock.RLock() mouseOver := w.mouseOver w.mouseLock.RUnlock() if mouseOver != nil { mouseOver.MouseOut() w.mouseLock.Lock() w.mouseOver = nil w.mouseLock.Unlock() } }) } func (w *window) processMouseClicked(button desktop.MouseButton, action action, modifiers fyne.KeyModifier) { w.mouseLock.RLock() w.mouseDragPos = w.mousePos mousePos := w.mousePos mouseDragStarted := w.mouseDragStarted w.mouseLock.RUnlock() if mousePos.IsZero() { // window may not be focused (darwin mostly) and so position callbacks not happening xpos, ypos := w.view().GetCursorPos() w.mouseLock.Lock() w.mousePos = fyne.NewPos(scale.ToFyneCoordinate(w.canvas, int(xpos)), scale.ToFyneCoordinate(w.canvas, int(ypos))) mousePos = w.mousePos w.mouseLock.Unlock() } co, pos, _ := w.findObjectAtPositionMatching(w.canvas, mousePos, func(object fyne.CanvasObject) bool { switch object.(type) { case fyne.Tappable, fyne.SecondaryTappable, fyne.DoubleTappable, fyne.Focusable, desktop.Mouseable, desktop.Hoverable: return true case fyne.Draggable: if mouseDragStarted { return true } } return false }) ev := &fyne.PointEvent{ Position: pos, AbsolutePosition: mousePos, } coMouse := co if wid, ok := co.(desktop.Mouseable); ok { mev := &desktop.MouseEvent{ Button: button, Modifier: modifiers, } mev.Position = ev.Position mev.AbsolutePosition = mousePos w.mouseClickedHandleMouseable(mev, action, wid) } if wid, ok := co.(fyne.Focusable); !ok || wid != w.canvas.Focused() { ignore := false _, _, _ = w.findObjectAtPositionMatching(w.canvas, mousePos, func(object fyne.CanvasObject) bool { switch object.(type) { case fyne.Focusable: ignore = true return true } return false }) if !ignore { // if a parent item under the mouse has focus then ignore this tap unfocus w.canvas.Unfocus() } } w.mouseLock.Lock() if action == press { w.mouseButton |= button } else if action == release { w.mouseButton &= ^button } mouseDragged := w.mouseDragged mouseDragStarted = w.mouseDragStarted mouseOver := w.mouseOver shouldMouseOut := w.objIsDragged(mouseOver) && !w.objIsDragged(coMouse) mousePressed := w.mousePressed w.mouseLock.Unlock() if action == release && mouseDragged != nil { if mouseDragStarted { w.QueueEvent(mouseDragged.DragEnd) w.mouseLock.Lock() w.mouseDragStarted = false w.mouseLock.Unlock() } if shouldMouseOut { w.mouseOut() } w.mouseLock.Lock() w.mouseDragged = nil w.mouseLock.Unlock() } _, tap := co.(fyne.Tappable) secondary, altTap := co.(fyne.SecondaryTappable) if tap || altTap { if action == press { w.mouseLock.Lock() w.mousePressed = co w.mouseLock.Unlock() } else if action == release { if co == mousePressed { if button == desktop.MouseButtonSecondary && altTap { w.QueueEvent(func() { secondary.TappedSecondary(ev) }) } } } } // Check for double click/tap on left mouse button if action == release && button == desktop.MouseButtonPrimary && !mouseDragStarted { w.mouseClickedHandleTapDoubleTap(co, ev) } } func (w *window) mouseClickedHandleMouseable(mev *desktop.MouseEvent, action action, wid desktop.Mouseable) { mousePos := mev.AbsolutePosition if action == press { w.QueueEvent(func() { wid.MouseDown(mev) }) } else if action == release { w.mouseLock.RLock() mouseDragged := w.mouseDragged mouseDraggedOffset := w.mouseDraggedOffset w.mouseLock.RUnlock() if mouseDragged == nil { w.QueueEvent(func() { wid.MouseUp(mev) }) } else { if dragged, ok := mouseDragged.(desktop.Mouseable); ok { mev.Position = mousePos.Subtract(mouseDraggedOffset) w.QueueEvent(func() { dragged.MouseUp(mev) }) } else { w.QueueEvent(func() { wid.MouseUp(mev) }) } } } } func (w *window) mouseClickedHandleTapDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { _, doubleTap := co.(fyne.DoubleTappable) if doubleTap { w.mouseLock.Lock() w.mouseClickCount++ w.mouseLastClick = co mouseCancelFunc := w.mouseCancelFunc w.mouseLock.Unlock() if mouseCancelFunc != nil { mouseCancelFunc() return } go w.waitForDoubleTap(co, ev) } else { w.mouseLock.Lock() if wid, ok := co.(fyne.Tappable); ok && co == w.mousePressed { w.QueueEvent(func() { wid.Tapped(ev) }) } w.mousePressed = nil w.mouseLock.Unlock() } } func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { var ctx context.Context w.mouseLock.Lock() ctx, w.mouseCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) defer w.mouseCancelFunc() w.mouseLock.Unlock() <-ctx.Done() w.mouseLock.Lock() defer w.mouseLock.Unlock() if w.mouseClickCount == 2 && w.mouseLastClick == co { if wid, ok := co.(fyne.DoubleTappable); ok { w.QueueEvent(func() { wid.DoubleTapped(ev) }) } } else if co == w.mousePressed { if wid, ok := co.(fyne.Tappable); ok { w.QueueEvent(func() { wid.Tapped(ev) }) } } w.mouseClickCount = 0 w.mousePressed = nil w.mouseCancelFunc = nil w.mouseLastClick = nil } func (w *window) processMouseScrolled(xoff float64, yoff float64) { w.mouseLock.RLock() mousePos := w.mousePos w.mouseLock.RUnlock() co, pos, _ := w.findObjectAtPositionMatching(w.canvas, mousePos, func(object fyne.CanvasObject) bool { _, ok := object.(fyne.Scrollable) return ok }) switch wid := co.(type) { case fyne.Scrollable: if math.Abs(xoff) >= scrollAccelerateCutoff { xoff *= scrollAccelerateRate } if math.Abs(yoff) >= scrollAccelerateCutoff { yoff *= scrollAccelerateRate } ev := &fyne.ScrollEvent{} ev.Scrolled = fyne.NewDelta(float32(xoff)*scrollSpeed, float32(yoff)*scrollSpeed) ev.Position = pos ev.AbsolutePosition = mousePos wid.Scrolled(ev) } } func (w *window) capturesTab(modifier fyne.KeyModifier) bool { captures := false if ent, ok := w.canvas.Focused().(fyne.Tabbable); ok { captures = ent.AcceptsTab() } if !captures { switch modifier { case 0: w.QueueEvent(w.canvas.FocusNext) return false case fyne.KeyModifierShift: w.QueueEvent(w.canvas.FocusPrevious) return false } } return captures } func (w *window) processKeyPressed(keyName fyne.KeyName, keyASCII fyne.KeyName, scancode int, action action, keyDesktopModifier fyne.KeyModifier) { keyEvent := &fyne.KeyEvent{Name: keyName, Physical: fyne.HardwareKey{ScanCode: scancode}} pendingMenuToggle := w.menuTogglePending pendingMenuDeactivation := w.menuDeactivationPending w.menuTogglePending = desktop.KeyNone w.menuDeactivationPending = desktop.KeyNone switch action { case release: if action == release && keyName != "" { switch keyName { case pendingMenuToggle: w.canvas.ToggleMenu() case pendingMenuDeactivation: if w.canvas.DismissMenu() { return } } } if w.canvas.Focused() != nil { if focused, ok := w.canvas.Focused().(desktop.Keyable); ok { w.QueueEvent(func() { focused.KeyUp(keyEvent) }) } } else if w.canvas.onKeyUp != nil { w.QueueEvent(func() { w.canvas.onKeyUp(keyEvent) }) } return // ignore key up in other core events case press: switch keyName { case desktop.KeyAltLeft, desktop.KeyAltRight: // compensate for GLFW modifiers bug https://github.com/glfw/glfw/issues/1630 if (runtime.GOOS == "linux" && keyDesktopModifier == 0) || (runtime.GOOS != "linux" && keyDesktopModifier == fyne.KeyModifierAlt) { w.menuTogglePending = keyName } case fyne.KeyEscape: w.menuDeactivationPending = keyName } if w.canvas.Focused() != nil { if focused, ok := w.canvas.Focused().(desktop.Keyable); ok { w.QueueEvent(func() { focused.KeyDown(keyEvent) }) } } else if w.canvas.onKeyDown != nil { w.QueueEvent(func() { w.canvas.onKeyDown(keyEvent) }) } default: // key repeat will fall through to TypedKey and TypedShortcut } modifierOtherThanShift := (keyDesktopModifier & fyne.KeyModifierControl) | (keyDesktopModifier & fyne.KeyModifierAlt) | (keyDesktopModifier & fyne.KeyModifierSuper) if (keyName == fyne.KeyTab && modifierOtherThanShift == 0 && !w.capturesTab(keyDesktopModifier)) || w.triggersShortcut(keyName, keyASCII, keyDesktopModifier) { return } // No shortcut detected, pass down to TypedKey focused := w.canvas.Focused() if focused != nil { w.QueueEvent(func() { focused.TypedKey(keyEvent) }) } else if w.canvas.onTypedKey != nil { w.QueueEvent(func() { w.canvas.onTypedKey(keyEvent) }) } } // charInput defines the character with modifiers callback which is called when a // Unicode character is input. // // Characters do not map 1:1 to physical keys, as a key may produce zero, one or more characters. func (w *window) processCharInput(char rune) { if focused := w.canvas.Focused(); focused != nil { w.QueueEvent(func() { focused.TypedRune(char) }) } else if w.canvas.onTypedRune != nil { w.QueueEvent(func() { w.canvas.onTypedRune(char) }) } } func (w *window) processFocused(focus bool) { if focus { if curWindow == nil { fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerEnteredForeground() } curWindow = w w.canvas.FocusGained() } else { w.canvas.FocusLost() w.mouseLock.Lock() w.mousePos = fyne.Position{} w.mouseLock.Unlock() go func() { // check whether another window was focused or not time.Sleep(time.Millisecond * 100) if curWindow != w { return } curWindow = nil fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerExitedForeground() }() } } func (w *window) triggersShortcut(localizedKeyName fyne.KeyName, key fyne.KeyName, modifier fyne.KeyModifier) bool { var shortcut fyne.Shortcut ctrlMod := fyne.KeyModifierControl if runtime.GOOS == "darwin" { ctrlMod = fyne.KeyModifierSuper } // User pressing physical keys Ctrl+V while using a Russian (or any non-ASCII) keyboard layout // is reported as a fyne.KeyUnknown key with Control modifier. We should still consider this // as a "Paste" shortcut. // See https://github.com/fyne-io/fyne/pull/2587 for discussion. keyName := localizedKeyName resemblesShortcut := (modifier&(fyne.KeyModifierControl|fyne.KeyModifierSuper) != 0) if (localizedKeyName == fyne.KeyUnknown) && resemblesShortcut { if key != fyne.KeyUnknown { keyName = key } } if modifier == ctrlMod { switch keyName { case fyne.KeyV: // detect paste shortcut shortcut = &fyne.ShortcutPaste{ Clipboard: w.Clipboard(), } case fyne.KeyC, fyne.KeyInsert: // detect copy shortcut shortcut = &fyne.ShortcutCopy{ Clipboard: w.Clipboard(), } case fyne.KeyX: // detect cut shortcut shortcut = &fyne.ShortcutCut{ Clipboard: w.Clipboard(), } case fyne.KeyA: // detect selectAll shortcut shortcut = &fyne.ShortcutSelectAll{} } } if modifier == fyne.KeyModifierShift { switch keyName { case fyne.KeyInsert: // detect paste shortcut shortcut = &fyne.ShortcutPaste{ Clipboard: w.Clipboard(), } case fyne.KeyDelete: // detect cut shortcut shortcut = &fyne.ShortcutCut{ Clipboard: w.Clipboard(), } } } if shortcut == nil && modifier != 0 && !isKeyModifier(keyName) && modifier != fyne.KeyModifierShift { shortcut = &desktop.CustomShortcut{ KeyName: keyName, Modifier: modifier, } } if shortcut != nil { if focused, ok := w.canvas.Focused().(fyne.Shortcutable); ok { shouldRunShortcut := true type selectableText interface { fyne.Disableable SelectedText() string } if selectableTextWid, ok := focused.(selectableText); ok && selectableTextWid.Disabled() { shouldRunShortcut = shortcut.ShortcutName() == "Copy" } if shouldRunShortcut { w.QueueEvent(func() { focused.TypedShortcut(shortcut) }) } return shouldRunShortcut } w.QueueEvent(func() { w.canvas.TypedShortcut(shortcut) }) return true } return false } func (w *window) RunWithContext(f func()) { if w.isClosing() { return } w.view().MakeContextCurrent() f() w.DetachCurrentContext() } func (w *window) RescaleContext() { runOnMain(w.rescaleOnMain) } func (w *window) Context() interface{} { return nil } func (w *window) runOnMainWhenCreated(fn func()) { if w.view() != nil { runOnMain(fn) return } w.pending = append(w.pending, fn) } func (d *gLDriver) CreateWindow(title string) fyne.Window { return d.createWindow(title, true) } func (d *gLDriver) createWindow(title string, decorate bool) fyne.Window { var ret *window if title == "" { title = defaultTitle } runOnMain(func() { d.initGLFW() ret = &window{title: title, decorate: decorate, driver: d} // This queue is destroyed when the window is closed. ret.InitEventQueue() go ret.RunEventQueue() ret.canvas = newCanvas() ret.canvas.context = ret ret.SetIcon(ret.icon) d.addWindow(ret) }) return ret } func (w *window) doShowAgain() { if w.isClosing() { return } runOnMain(func() { // show top canvas element if content := w.canvas.Content(); content != nil { content.Show() } view := w.view() view.SetPos(w.xpos, w.ypos) view.Show() w.viewLock.Lock() w.visible = true w.viewLock.Unlock() }) } func (w *window) isClosing() bool { w.viewLock.RLock() closing := w.closing || w.viewport == nil w.viewLock.RUnlock() return closing } func (d *gLDriver) CreateSplashWindow() fyne.Window { win := d.createWindow("", false) win.SetPadded(false) win.CenterOnScreen() return win } func (d *gLDriver) AllWindows() []fyne.Window { return d.windows } func isKeyModifier(keyName fyne.KeyName) bool { return keyName == desktop.KeyShiftLeft || keyName == desktop.KeyShiftRight || keyName == desktop.KeyControlLeft || keyName == desktop.KeyControlRight || keyName == desktop.KeyAltLeft || keyName == desktop.KeyAltRight || keyName == desktop.KeySuperLeft || keyName == desktop.KeySuperRight }