Skip to content

Commit

Permalink
Pull in hyperlink fix from fyne-io/fyne/4320
Browse files Browse the repository at this point in the history
  • Loading branch information
dweymouth committed Nov 3, 2023
1 parent e7b024a commit 1f96a69
Showing 1 changed file with 71 additions and 13 deletions.
84 changes: 71 additions & 13 deletions widget/hyperlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Hyperlink struct {
// Since: 2.2
OnTapped func() `json:"-"`

textSize fyne.Size // updated in syncSegments
focused, hovered bool
provider *RichText
}
Expand Down Expand Up @@ -67,7 +68,10 @@ func (hl *Hyperlink) CreateRenderer() fyne.WidgetRenderer {

// Cursor returns the cursor type of this widget
func (hl *Hyperlink) Cursor() desktop.Cursor {
return desktop.PointerCursor
if hl.hovered {
return desktop.PointerCursor
}
return desktop.DefaultCursor
}

// FocusGained is a hook called by the focus handling logic after this object gained the focus.
Expand All @@ -83,19 +87,58 @@ func (hl *Hyperlink) FocusLost() {
}

// MouseIn is a hook that is called if the mouse pointer enters the element.
func (hl *Hyperlink) MouseIn(*desktop.MouseEvent) {
hl.hovered = true
hl.BaseWidget.Refresh()
func (hl *Hyperlink) MouseIn(e *desktop.MouseEvent) {
hl.MouseMoved(e)
}

// MouseMoved is a hook that is called if the mouse pointer moved over the element.
func (hl *Hyperlink) MouseMoved(*desktop.MouseEvent) {
func (hl *Hyperlink) MouseMoved(e *desktop.MouseEvent) {
oldHovered := hl.hovered
hl.hovered = hl.isPosOverText(e.Position)
if hl.hovered != oldHovered {
hl.BaseWidget.Refresh()
}
}

// MouseOut is a hook that is called if the mouse pointer leaves the element.
func (hl *Hyperlink) MouseOut() {
changed := hl.hovered
hl.hovered = false
hl.BaseWidget.Refresh()
if changed {
hl.BaseWidget.Refresh()
}
}

func (hl *Hyperlink) focusWidth() float32 {
innerPad := theme.InnerPadding()
return fyne.Min(hl.size.Width, hl.textSize.Width+innerPad+theme.Padding()*2) - innerPad
}

func (hl *Hyperlink) focusXPos() float32 {
switch hl.Alignment {
case fyne.TextAlignLeading:
return theme.InnerPadding() / 2
case fyne.TextAlignCenter:
return (hl.size.Width - hl.focusWidth()) / 2
case fyne.TextAlignTrailing:
return (hl.size.Width - hl.focusWidth()) - theme.InnerPadding()/2
default:
return 0 // unreached
}
}

func (hl *Hyperlink) isPosOverText(pos fyne.Position) bool {
innerPad := theme.InnerPadding()
pad := theme.Padding()
// If not rendered yet provider will be nil
lineCount := float32(1)
if hl.provider != nil {
lineCount = fyne.Max(lineCount, float32(len(hl.provider.rowBounds)))
}

xpos := hl.focusXPos()
return pos.X >= xpos && pos.X <= xpos+hl.focusWidth() &&
pos.Y >= innerPad/2 && pos.Y <= hl.textSize.Height*lineCount+pad*2+innerPad/2
}

// Refresh triggers a redraw of the hyperlink.
Expand Down Expand Up @@ -156,12 +199,20 @@ func (hl *Hyperlink) SetURLFromString(str string) error {
}

// Tapped is called when a pointer tapped event is captured and triggers any change handler
func (hl *Hyperlink) Tapped(*fyne.PointEvent) {
func (hl *Hyperlink) Tapped(e *fyne.PointEvent) {
// If not rendered yet (hl.provider == nil), register all taps
// in practice this probably only happens in our unit tests
if hl.provider != nil && !hl.isPosOverText(e.Position) {
return
}
hl.invokeAction()
}

func (hl *Hyperlink) invokeAction() {
if hl.OnTapped != nil {
hl.OnTapped()
return
}

hl.openURL()
}

Expand All @@ -172,7 +223,7 @@ func (hl *Hyperlink) TypedRune(rune) {
// TypedKey is a hook called by the input handling logic on key events if this object is focused.
func (hl *Hyperlink) TypedKey(ev *fyne.KeyEvent) {
if ev.Name == fyne.KeySpace {
hl.Tapped(nil)
hl.invokeAction()
}
}

Expand All @@ -196,6 +247,7 @@ func (hl *Hyperlink) syncSegments() {
},
Text: hl.Text,
}}
hl.textSize = fyne.MeasureText(hl.Text, theme.TextSize(), hl.TextStyle)
}

var _ fyne.WidgetRenderer = (*hyperlinkRenderer)(nil)
Expand All @@ -212,11 +264,17 @@ func (r *hyperlinkRenderer) Destroy() {
}

func (r *hyperlinkRenderer) Layout(s fyne.Size) {
innerPad := theme.InnerPadding()
w := r.hl.focusWidth()
xposFocus := r.hl.focusXPos()
xposUnderline := xposFocus + innerPad/2

r.hl.provider.Resize(s)
r.focus.Move(fyne.NewPos(theme.InnerPadding()/2, theme.InnerPadding()/2))
r.focus.Resize(fyne.NewSize(s.Width-theme.InnerPadding(), s.Height-theme.InnerPadding()))
r.under.Move(fyne.NewPos(theme.InnerPadding(), s.Height-theme.InnerPadding()))
r.under.Resize(fyne.NewSize(s.Width-theme.InnerPadding()*2, 1))
lineCount := float32(len(r.hl.provider.rowBounds))
r.focus.Move(fyne.NewPos(xposFocus, innerPad/2))
r.focus.Resize(fyne.NewSize(w, r.hl.textSize.Height*lineCount+innerPad))
r.under.Move(fyne.NewPos(xposUnderline, r.hl.textSize.Height*lineCount+theme.Padding()*2))
r.under.Resize(fyne.NewSize(w-innerPad, 1))
}

func (r *hyperlinkRenderer) MinSize() fyne.Size {
Expand Down

0 comments on commit 1f96a69

Please sign in to comment.