Skip to content

Commit

Permalink
Hover effect for frame buttons
Browse files Browse the repository at this point in the history
Also required updating frame code which is now more efficient - re-use canvases :)
  • Loading branch information
andydotxyz committed Sep 28, 2020
1 parent 4caacb2 commit 137da79
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 24 deletions.
61 changes: 51 additions & 10 deletions internal/x11/win/frame.go
Expand Up @@ -9,6 +9,7 @@ import (

"fyne.io/fyne"
"fyne.io/fyne/driver/desktop"
"fyne.io/fyne/test"
"fyne.io/fyne/theme"
"fyne.io/fyne/tools/playground"

Expand Down Expand Up @@ -37,10 +38,11 @@ type frame struct {
borderTop, borderTopRight xproto.Pixmap
borderTopWidth uint16

hovered desktop.Hoverable
clickCount int
cancelFunc context.CancelFunc

canvas fyne.Canvas
canvas test.WindowlessCanvas
client *client
}

Expand Down Expand Up @@ -321,22 +323,31 @@ func (f *frame) drawDecoration(pidTop xproto.Pixmap, drawTop xproto.Gcontext, pi
screen := fynedesk.Instance().Screens().ScreenForWindow(f.client)
scale := screen.CanvasScale()

canvas := playground.NewSoftwareCanvas()
canvas.SetScale(scale)
canvas.SetPadded(false)
canMaximize := true
if windowSizeFixed(f.client.wm.X(), f.client.win) ||
!windowSizeCanMaximize(f.client.wm.X(), f.client) {
canMaximize = false
}
canvas.SetContent(wm.NewBorder(f.client, f.client.Properties().Icon(), canMaximize))
f.canvas = canvas

if f.canvas == nil {
canvas := playground.NewSoftwareCanvas()
canvas.SetPadded(false)

canvas.SetContent(wm.NewBorder(f.client, f.client.Properties().Icon(), canMaximize))
f.canvas = canvas
} else {
b := f.canvas.Content().(*wm.Border)
b.SetFocused(f.client.Focused())
b.SetTitle(f.client.props.Title())
// TODO maybe update icon?
}
f.canvas.SetScale(scale)

heightPix := x11.TitleHeight(x11.XWin(f.client))
iconBorderPixWidth := heightPix + x11.BorderWidth(x11.XWin(f.client))*2
widthPix := f.borderTopWidth + iconBorderPixWidth
canvas.Resize(fyne.NewSize(int(float32(widthPix)/scale)+1, wmTheme.TitleHeight))
img := canvas.Capture()
f.canvas.Resize(fyne.NewSize(int(float32(widthPix)/scale)+1, wmTheme.TitleHeight))
img := f.canvas.Capture()

// TODO just copy the label minSize - smallest possible but maybe bigger than window width
// Draw in pixel rows so we don't overflow count usable by PutImageChecked
Expand Down Expand Up @@ -485,24 +496,54 @@ func (f *frame) mouseMotion(x, y int16) {
relX := x - f.x
relY := y - f.y

refresh := false
obj := wm.FindObjectAtPixelPositionMatching(int(relX), int(relY), f.canvas,
func(obj fyne.CanvasObject) bool {
_, ok := obj.(desktop.Cursorable)
if ok {
return true
}

_, ok = obj.(desktop.Hoverable)
return ok
},
)

cursor := x11.DefaultCursor
if obj != nil {
if obj.(desktop.Cursorable).Cursor() == wm.CloseCursor {
cursor = x11.CloseCursor
if cur, ok := obj.(desktop.Cursorable); ok {
if cur.Cursor() == wm.CloseCursor {
cursor = x11.CloseCursor
}
}
if hov, ok := obj.(desktop.Hoverable); ok {
if f.hovered == nil {
hov.MouseIn(&desktop.MouseEvent{})
f.hovered = hov
refresh = true
} else if obj.(desktop.Hoverable) != f.hovered {
f.hovered.MouseOut()
hov.MouseIn(&desktop.MouseEvent{})
f.hovered = hov
refresh = true
} else {
hov.MouseMoved(&desktop.MouseEvent{})
}
}
} else if uint16(relY) > titleHeight {
if !f.client.Maximized() && !f.client.Fullscreened() && !windowSizeFixed(f.client.wm.X(), f.client.win) {
cursor = f.lookupResizeCursor(relX, relY)
}
}

if obj == nil && f.hovered != nil {
f.hovered.MouseOut()
f.hovered = nil
refresh = true
}
if refresh {
f.applyTheme(true)
}
err := xproto.ChangeWindowAttributesChecked(f.client.wm.Conn(), f.client.id, xproto.CwCursor,
[]uint32{uint32(cursor)}).Check()
if err != nil {
Expand Down
42 changes: 30 additions & 12 deletions wm/border.go
Expand Up @@ -21,7 +21,7 @@ func makeFiller(width int) fyne.CanvasObject {
}

// NewBorder creates a new window border for the given window details
func NewBorder(win fynedesk.Window, icon fyne.Resource, canMaximize bool) fyne.CanvasObject {
func NewBorder(win fynedesk.Window, icon fyne.Resource, canMaximize bool) *Border {
desk := fynedesk.Instance()

if icon == nil {
Expand Down Expand Up @@ -50,12 +50,14 @@ func NewBorder(win fynedesk.Window, icon fyne.Resource, canMaximize bool) fyne.C
win.Iconify()
})
min.HideShadow = true
title := widget.NewLabel(win.Properties().Title())
titleBar := newColoredHBox(win.Focused(), win, makeFiller(0),
newCloseButton(win),
max,
min,
widget.NewLabel(win.Properties().Title()),
title,
layout.NewSpacer())
titleBar.title = title

if icon != nil {
appIcon := canvas.NewImageFromResource(icon)
Expand All @@ -64,44 +66,60 @@ func NewBorder(win fynedesk.Window, icon fyne.Resource, canMaximize bool) fyne.C
titleBar.Append(makeFiller(1))
}

return fyne.NewContainerWithLayout(layout.NewBorderLayout(titleBar, nil, nil, nil),
titleBar)
return titleBar
}

type coloredHBox struct {
// Border represents a window border. It draws the title bar and provides functions to manipulate it.
type Border struct {
*widget.Box
focused bool
title *widget.Label
win fynedesk.Window
}

type coloredBoxRenderer struct {
fyne.WidgetRenderer
focused bool
b *Border
}

func (c *coloredHBox) DoubleTapped(*fyne.PointEvent) {
// DoubleTapped is called when the user double taps a frame, it toggles the maximised state.
func (c *Border) DoubleTapped(*fyne.PointEvent) {
if c.win.Maximized() {
c.win.Unmaximize()
return
}
c.win.Maximize()
}

func (c *coloredHBox) CreateRenderer() fyne.WidgetRenderer {
render := &coloredBoxRenderer{focused: c.focused}
// CreateRenderer creates a new renderer for this border
//
// Implements: fyne.Widget
func (c *Border) CreateRenderer() fyne.WidgetRenderer {
render := &coloredBoxRenderer{b: c}
render.WidgetRenderer = c.Box.CreateRenderer()
return render
}

// SetFocused specifies whether this window is focused, and updates visuals accordingly.
func (c *Border) SetFocused(focus bool) {
c.focused = focus
c.Refresh()
}

// SetTitle updates the title portion of this border and refreshes.
func (c *Border) SetTitle(title string) {
c.title.SetText(title)
}

func (r *coloredBoxRenderer) BackgroundColor() color.Color {
if r.focused {
if r.b.focused {
return theme.BackgroundColor()
}
return theme.DisabledButtonColor()
}

func newColoredHBox(focused bool, win fynedesk.Window, objs ...fyne.CanvasObject) *coloredHBox {
ret := &coloredHBox{focused: focused, win: win}
func newColoredHBox(focused bool, win fynedesk.Window, objs ...fyne.CanvasObject) *Border {
ret := &Border{focused: focused, win: win}
ret.Box = widget.NewHBox(objs...)
ret.ExtendBaseWidget(ret)

Expand Down
15 changes: 13 additions & 2 deletions wm/button.go
Expand Up @@ -20,6 +20,19 @@ func (c *closeButton) Cursor() desktop.Cursor {
return CloseCursor
}

func (c *closeButton) MouseIn(*desktop.MouseEvent) {
c.Style = widget.PrimaryButton
c.Refresh()
}

func (c *closeButton) MouseMoved(*desktop.MouseEvent) {
}

func (c *closeButton) MouseOut() {
c.Style = widget.DefaultButton
c.Refresh()
}

func newCloseButton(win fynedesk.Window) *closeButton {
b := &closeButton{}
b.ExtendBaseWidget(b)
Expand All @@ -29,7 +42,5 @@ func newCloseButton(win fynedesk.Window) *closeButton {
}

b.Icon = theme.CancelIcon()
b.Style = widget.PrimaryButton

return b
}

0 comments on commit 137da79

Please sign in to comment.