New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Keyboard only operation #1074
Changes from all commits
ed04aa9
0b06b60
5bb4487
07c8cbf
c32a82c
c416a0d
c8052c9
b61755f
f6ec6d1
13ec8ff
0a75eb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,11 +16,41 @@ var _ fyne.Widget = (*MenuBar)(nil) | |
// MenuBar is a widget for displaying a fyne.MainMenu in a bar. | ||
type MenuBar struct { | ||
widget.Base | ||
Items []fyne.CanvasObject | ||
|
||
active bool | ||
activeItem *menuBarItem | ||
canvas fyne.Canvas | ||
Items []fyne.CanvasObject | ||
currentItem int | ||
active bool | ||
canvas fyne.Canvas | ||
activeItem *menuBarItem | ||
} | ||
|
||
// handleKey will process keys pressed when menu is open | ||
func (b *MenuBar) handleKey(key fyne.KeyName) { | ||
if key == fyne.KeyEscape { | ||
b.deactivate() | ||
} else if key == fyne.KeyEnter || key == fyne.KeyReturn || key == fyne.KeySpace { | ||
// Simulate tapping on menu entry | ||
b.Items[b.currentItem].(*menuBarItem).Child().HandleEnterKey() | ||
} else if key == fyne.KeyRight { | ||
// Move to the menu item at right | ||
if b.currentItem < len(b.Items)-1 { | ||
b.Items[b.currentItem].(*menuBarItem).Tapped(nil) | ||
b.currentItem++ | ||
b.Items[b.currentItem].(*menuBarItem).Tapped(nil) | ||
} | ||
} else if key == fyne.KeyLeft { | ||
// Move to the menu item at left | ||
if b.currentItem > 0 { | ||
b.Items[b.currentItem].(*menuBarItem).Tapped(nil) | ||
b.currentItem-- | ||
b.Items[b.currentItem].(*menuBarItem).Tapped(nil) | ||
} | ||
} else if key == fyne.KeyUp { | ||
// Move focus to menu item above | ||
b.Items[b.currentItem].(*menuBarItem).Child().HandleUpKey() | ||
} else if key == fyne.KeyDown { | ||
// Move focus to menu item below | ||
b.Items[b.currentItem].(*menuBarItem).Child().HandleDownKey() | ||
} | ||
} | ||
|
||
// NewMenuBar creates a menu bar populated with items from the passed main menu structure. | ||
|
@@ -114,7 +144,6 @@ func (b *MenuBar) deactivate() { | |
if !b.active { | ||
return | ||
} | ||
|
||
b.active = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dupe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
if b.activeItem != nil { | ||
defer b.activeItem.Child().Dismiss() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,11 +112,22 @@ func (i *menuBarItem) Show() { | |
// Tapped toggles the activation state of the menu bar. | ||
// It shows the item’s menu if the bar is activated and hides it if the bar is deactivated. | ||
// Implements: fyne.Tappable | ||
func (i *menuBarItem) Tapped(*fyne.PointEvent) { | ||
func (i *menuBarItem) Tapped(pos *fyne.PointEvent) { | ||
if i.Parent.active { | ||
i.Parent.deactivate() | ||
} else { | ||
// Set then menu bar's index to the tapped items index and update focus | ||
i.Parent.activateChild(i) | ||
for j := 0; j < len(i.Parent.Items); j++ { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this loop... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have changed the name "index" to "currentItem", as it indicates the menu item selected. There is unfortunately a mix of "focused". The focused item receiveing the keyboard data is the MenuBar, while the indicated (focus-colored) item is one of the menu items in the list. Perhaps each menu item should recieve keyboard input? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. THat might be cleaner, would probably help with code encapsulation, that said left/right may still make sense at the top level. |
||
if i.Parent.Items[j] == i { | ||
i.Parent.currentItem = j | ||
if pos == nil { | ||
i.Parent.Items[i.Parent.currentItem].(*menuBarItem).Child().HandleUpKey() | ||
} else { | ||
i.Parent.Items[i.Parent.currentItem].(*menuBarItem).Child().Defocus() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
@@ -130,7 +141,6 @@ func (r *menuBarItemRenderer) BackgroundColor() color.Color { | |
if r.i.hovered || (r.i.child != nil && r.i.child.Visible()) { | ||
return theme.HoverColor() | ||
} | ||
|
||
return color.Transparent | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import ( | |
"fyne.io/fyne/internal/cache" | ||
"fyne.io/fyne/internal/driver" | ||
"fyne.io/fyne/internal/painter/gl" | ||
"fyne.io/fyne/widget" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We must not add dependencies from |
||
|
||
"github.com/go-gl/glfw/v3.3/glfw" | ||
) | ||
|
@@ -622,7 +623,13 @@ func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw. | |
} | ||
|
||
if layer != 1 { // 0 - overlay, 1 - menu, 2 - content | ||
if wid, ok := co.(fyne.Focusable); ok { | ||
if _, isToolbarButton := co.(*widget.ToolbarButton); isToolbarButton { | ||
// Avoid changing focus when a toolbar button is tapped | ||
w.canvas.Unfocus() | ||
} else if _, isButton := co.(*widget.Button); isButton { | ||
// Avoid changing focus when an ordinary button is tapped | ||
w.canvas.Unfocus() | ||
} else if wid, ok := co.(fyne.Focusable); ok { | ||
w.canvas.Focus(wid) | ||
} else { | ||
w.canvas.Unfocus() | ||
|
@@ -857,16 +864,29 @@ func (w *window) keyPressed(viewport *glfw.Window, key glfw.Key, scancode int, a | |
keyEvent := &fyne.KeyEvent{Name: keyName} | ||
keyDesktopModifier := desktopModifier(mods) | ||
|
||
if keyName == fyne.KeyTab { | ||
if keyDesktopModifier == 0 { | ||
if action != glfw.Release { | ||
w.canvas.focusMgr.FocusNext(w.canvas.focused) | ||
// Activate the menu when the Alt key is pressed | ||
focused := w.canvas.Focused() | ||
if menu, ok := w.canvas.menu.(*MenuBar); ok { | ||
if !menu.active { | ||
if action == glfw.Press && (key == glfw.KeyLeftAlt || key == glfw.KeyRightAlt) && | ||
keyDesktopModifier == desktop.AltModifier { | ||
mbi := menu.Items[0].(*menuBarItem) | ||
mbi.Tapped(nil) | ||
} | ||
} else { | ||
if action == glfw.Release { | ||
menu.handleKey(keyName) | ||
} | ||
return | ||
} | ||
} | ||
|
||
if keyName == fyne.KeyTab && action != glfw.Release { | ||
if keyDesktopModifier == 0 { | ||
w.canvas.focusMgr.FocusNext(w.canvas.focused) | ||
return | ||
} else if keyDesktopModifier == desktop.ShiftModifier { | ||
if action != glfw.Release { | ||
w.canvas.focusMgr.FocusPrevious(w.canvas.focused) | ||
} | ||
w.canvas.focusMgr.FocusPrevious(w.canvas.focused) | ||
return | ||
} | ||
} | ||
|
@@ -950,7 +970,6 @@ func (w *window) keyPressed(viewport *glfw.Window, key glfw.Key, scancode int, a | |
} | ||
|
||
// 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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,44 @@ func DarkTheme() fyne.Theme { | |
return theme | ||
} | ||
|
||
// Shade will darken a light color and lighten a light color by the given % value | ||
// should use 4,8, 12 or 14 percent darken/lighten to implement google material design rules | ||
// Note that the percent lightening is twice the percent given. See material.io/design/interaction/states. | ||
func Shade(c color.Color, pct uint32) color.Color { | ||
if pct == 0 { | ||
return c | ||
} | ||
r, g, b, a := c.RGBA() | ||
if pct > 50 { | ||
pct = 50 | ||
} | ||
if r+g+b < 3*0x8080 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it seems like this might be important, should it be extracted into something like an |
||
// Lighten by twice the percent given. | ||
return color.NRGBA{ | ||
R: uint8((r + (0x10000-r)*pct/50) >> 8), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. percent / 50 is a strange seemting thing to do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment above about shades. |
||
G: uint8((g + (0x10000-g)*pct/50) >> 8), | ||
B: uint8((b + (0x10000-b)*pct/50) >> 8), | ||
A: uint8(a >> 8), | ||
} | ||
} | ||
// Darken by given percent | ||
return color.NRGBA{ | ||
R: uint8((r * (100 - pct) / 100) >> 8), | ||
G: uint8((g * (100 - pct) / 100) >> 8), | ||
B: uint8((b * (100 - pct) / 100) >> 8), | ||
A: uint8(a >> 8), | ||
} | ||
} | ||
|
||
// PressedShade is the shade used for pressed buttons, in % | ||
const PressedShade = 14 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these percentages? should they be public API? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, they are percentages. I belive they should be part of the customizable theme, but I do not see how to do it. |
||
|
||
// HoveredShade is the shade used for hovered buttons, in % | ||
const HoveredShade = 4 | ||
|
||
// FocusedShade is the shade used for focused buttons, in %. | ||
const FocusedShade = 8 | ||
|
||
func (t *builtinTheme) BackgroundColor() color.Color { | ||
return t.background | ||
} | ||
|
@@ -117,7 +155,7 @@ func (t *builtinTheme) HoverColor() color.Color { | |
|
||
// FocusColor returns the color used to highlight a focused widget | ||
func (t *builtinTheme) FocusColor() color.Color { | ||
return t.primary | ||
return Shade(t.primary, FocusedShade) | ||
} | ||
|
||
// ScrollBarColor returns the color (and translucency) for a scrollBar | ||
|
@@ -276,6 +314,16 @@ func HoverColor() color.Color { | |
return current().HoverColor() | ||
} | ||
|
||
// PressedColor returns the colour used for a pressed button | ||
func PressedColor() color.Color { | ||
return Shade(FocusColor(), PressedShade) | ||
} | ||
|
||
// HoverFocusedColor returns the colour used for a focused/primary hovered button | ||
func HoverFocusedColor() color.Color { | ||
return Shade(FocusColor(), HoveredShade) | ||
} | ||
|
||
// FocusColor returns the color used to highlight a focussed widget | ||
func FocusColor() color.Color { | ||
return current().FocusColor() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this is demoing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was introduced to test keyboard operation in a second window. There was no extra windows with any focusable widgets, so I had to introduce some buttons to test it. Of cause it failed the first time I tested it, but should be working ok now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All windows should behave the same though?