• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fyne-io / fyne / 16167815158

09 Jul 2025 11:13AM UTC coverage: 62.401% (+0.09%) from 62.31%
16167815158

Pull #5802

github

cwarden
Make Scaled Monitor Size a fyne.Size
Pull Request #5802: Use Scaled Monitor Size For Monitor Detection

15 of 18 new or added lines in 1 file covered. (83.33%)

685 existing lines in 28 files now uncovered.

25212 of 40403 relevant lines covered (62.4%)

718.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.62
/widget/richtext_objects.go
1
package widget
2

3
import (
4
        "image/color"
5
        "net/url"
6
        "strconv"
7

8
        "fyne.io/fyne/v2"
9
        "fyne.io/fyne/v2/canvas"
10
        "fyne.io/fyne/v2/internal/scale"
11
        "fyne.io/fyne/v2/theme"
12
)
13

14
var (
15
        // RichTextStyleBlockquote represents a quote presented in an indented block.
16
        //
17
        // Since: 2.1
18
        RichTextStyleBlockquote = RichTextStyle{
19
                ColorName: theme.ColorNameForeground,
20
                Inline:    false,
21
                SizeName:  theme.SizeNameText,
22
                TextStyle: fyne.TextStyle{Italic: true},
23
        }
24
        // RichTextStyleCodeBlock represents a code blog segment.
25
        //
26
        // Since: 2.1
27
        RichTextStyleCodeBlock = RichTextStyle{
28
                ColorName: theme.ColorNameForeground,
29
                Inline:    false,
30
                SizeName:  theme.SizeNameText,
31
                TextStyle: fyne.TextStyle{Monospace: true},
32
        }
33
        // RichTextStyleCodeInline represents an inline code segment.
34
        //
35
        // Since: 2.1
36
        RichTextStyleCodeInline = RichTextStyle{
37
                ColorName: theme.ColorNameForeground,
38
                Inline:    true,
39
                SizeName:  theme.SizeNameText,
40
                TextStyle: fyne.TextStyle{Monospace: true},
41
        }
42
        // RichTextStyleEmphasis represents regular text with emphasis.
43
        //
44
        // Since: 2.1
45
        RichTextStyleEmphasis = RichTextStyle{
46
                ColorName: theme.ColorNameForeground,
47
                Inline:    true,
48
                SizeName:  theme.SizeNameText,
49
                TextStyle: fyne.TextStyle{Italic: true},
50
        }
51
        // RichTextStyleHeading represents a heading text that stands on its own line.
52
        //
53
        // Since: 2.1
54
        RichTextStyleHeading = RichTextStyle{
55
                ColorName: theme.ColorNameForeground,
56
                Inline:    false,
57
                SizeName:  theme.SizeNameHeadingText,
58
                TextStyle: fyne.TextStyle{Bold: true},
59
        }
60
        // RichTextStyleInline represents standard text that can be surrounded by other elements.
61
        //
62
        // Since: 2.1
63
        RichTextStyleInline = RichTextStyle{
64
                ColorName: theme.ColorNameForeground,
65
                Inline:    true,
66
                SizeName:  theme.SizeNameText,
67
        }
68
        // RichTextStyleParagraph represents standard text that should appear separate from other text.
69
        //
70
        // Since: 2.1
71
        RichTextStyleParagraph = RichTextStyle{
72
                ColorName: theme.ColorNameForeground,
73
                Inline:    false,
74
                SizeName:  theme.SizeNameText,
75
        }
76
        // RichTextStylePassword represents standard sized text where the characters are obscured.
77
        //
78
        // Since: 2.1
79
        RichTextStylePassword = RichTextStyle{
80
                ColorName: theme.ColorNameForeground,
81
                Inline:    true,
82
                SizeName:  theme.SizeNameText,
83
                concealed: true,
84
        }
85
        // RichTextStyleStrong represents regular text with a strong emphasis.
86
        //
87
        // Since: 2.1
88
        RichTextStyleStrong = RichTextStyle{
89
                ColorName: theme.ColorNameForeground,
90
                Inline:    true,
91
                SizeName:  theme.SizeNameText,
92
                TextStyle: fyne.TextStyle{Bold: true},
93
        }
94
        // RichTextStyleSubHeading represents a sub-heading text that stands on its own line.
95
        //
96
        // Since: 2.1
97
        RichTextStyleSubHeading = RichTextStyle{
98
                ColorName: theme.ColorNameForeground,
99
                Inline:    false,
100
                SizeName:  theme.SizeNameSubHeadingText,
101
                TextStyle: fyne.TextStyle{Bold: true},
102
        }
103
)
104

105
// HyperlinkSegment represents a hyperlink within a rich text widget.
106
//
107
// Since: 2.1
108
type HyperlinkSegment struct {
109
        Alignment fyne.TextAlign
110
        Text      string
111
        URL       *url.URL
112

113
        // OnTapped overrides the default `fyne.OpenURL` call when the link is tapped
114
        //
115
        // Since: 2.4
116
        OnTapped func() `json:"-"`
117
}
118

119
// Inline returns true as hyperlinks are inside other elements.
120
func (h *HyperlinkSegment) Inline() bool {
11✔
121
        return true
11✔
122
}
11✔
123

124
// Textual returns the content of this segment rendered to plain text.
125
func (h *HyperlinkSegment) Textual() string {
3✔
126
        return h.Text
3✔
127
}
3✔
128

129
// Visual returns a new instance of a hyperlink widget required to render this segment.
130
func (h *HyperlinkSegment) Visual() fyne.CanvasObject {
2✔
131
        link := NewHyperlink(h.Text, h.URL)
2✔
132
        link.Alignment = h.Alignment
2✔
133
        link.OnTapped = h.OnTapped
2✔
134
        return &fyne.Container{Layout: &unpadTextWidgetLayout{parent: link}, Objects: []fyne.CanvasObject{link}}
2✔
135
}
2✔
136

137
// Update applies the current state of this hyperlink segment to an existing visual.
138
func (h *HyperlinkSegment) Update(o fyne.CanvasObject) {
3✔
139
        link := o.(*fyne.Container).Objects[0].(*Hyperlink)
3✔
140
        link.Text = h.Text
3✔
141
        link.URL = h.URL
3✔
142
        link.Alignment = h.Alignment
3✔
143
        link.OnTapped = h.OnTapped
3✔
144
        link.Refresh()
3✔
145
}
3✔
146

147
// Select tells the segment that the user is selecting the content between the two positions.
148
func (h *HyperlinkSegment) Select(begin, end fyne.Position) {
×
149
        // no-op: this will be added when we progress to editor
×
150
}
×
151

152
// SelectedText should return the text representation of any content currently selected through the Select call.
153
func (h *HyperlinkSegment) SelectedText() string {
×
154
        // no-op: this will be added when we progress to editor
×
155
        return ""
×
156
}
×
157

158
// Unselect tells the segment that the user is has cancelled the previous selection.
159
func (h *HyperlinkSegment) Unselect() {
×
160
        // no-op: this will be added when we progress to editor
×
161
}
×
162

163
// ImageSegment represents an image within a rich text widget.
164
//
165
// Since: 2.3
166
type ImageSegment struct {
167
        Source fyne.URI
168
        Title  string
169

170
        // Alignment specifies the horizontal alignment of this image segment
171
        // Since: 2.4
172
        Alignment fyne.TextAlign
173
}
174

175
// Inline returns false as images in rich text are blocks.
176
func (i *ImageSegment) Inline() bool {
9✔
177
        return false
9✔
178
}
9✔
179

180
// Textual returns the content of this segment rendered to plain text.
181
func (i *ImageSegment) Textual() string {
×
182
        return "Image " + i.Title
×
183
}
×
184

185
// Visual returns a new instance of an image widget required to render this segment.
186
func (i *ImageSegment) Visual() fyne.CanvasObject {
1✔
187
        return newRichImage(i.Source, i.Alignment)
1✔
188
}
1✔
189

190
// Update applies the current state of this image segment to an existing visual.
191
func (i *ImageSegment) Update(o fyne.CanvasObject) {
4✔
192
        newer := canvas.NewImageFromURI(i.Source)
4✔
193
        img := o.(*richImage)
4✔
194

4✔
195
        // one of the following will be used
4✔
196
        img.img.File = newer.File
4✔
197
        img.img.Resource = newer.Resource
4✔
198
        img.setAlign(i.Alignment)
4✔
199

4✔
200
        img.Refresh()
4✔
201
}
4✔
202

203
// Select tells the segment that the user is selecting the content between the two positions.
204
func (i *ImageSegment) Select(begin, end fyne.Position) {
×
205
        // no-op: this will be added when we progress to editor
×
206
}
×
207

208
// SelectedText should return the text representation of any content currently selected through the Select call.
209
func (i *ImageSegment) SelectedText() string {
×
210
        // no-op: images have no text rendering
×
211
        return ""
×
212
}
×
213

214
// Unselect tells the segment that the user is has cancelled the previous selection.
215
func (i *ImageSegment) Unselect() {
×
216
        // no-op: this will be added when we progress to editor
×
217
}
×
218

219
// ListSegment includes an itemised list with the content set using the Items field.
220
//
221
// Since: 2.1
222
type ListSegment struct {
223
        Items   []RichTextSegment
224
        Ordered bool
225
}
226

227
// Inline returns false as a list should be in a block.
228
func (l *ListSegment) Inline() bool {
×
229
        return false
×
230
}
×
231

232
// Segments returns the segments required to draw bullets before each item
233
func (l *ListSegment) Segments() []RichTextSegment {
3✔
234
        out := make([]RichTextSegment, len(l.Items))
3✔
235
        for i, in := range l.Items {
8✔
236
                txt := "• "
5✔
237
                if l.Ordered {
9✔
238
                        txt = strconv.Itoa(i+1) + "."
4✔
239
                }
4✔
240
                bullet := &TextSegment{Text: txt + " ", Style: RichTextStyleStrong}
5✔
241
                out[i] = &ParagraphSegment{Texts: []RichTextSegment{
5✔
242
                        bullet,
5✔
243
                        in,
5✔
244
                }}
5✔
245
        }
246
        return out
3✔
247
}
248

249
// Textual returns no content for a list as the content is in sub-segments.
250
func (l *ListSegment) Textual() string {
×
251
        return ""
×
252
}
×
253

254
// Visual returns no additional elements for this segment.
255
func (l *ListSegment) Visual() fyne.CanvasObject {
×
256
        return nil
×
257
}
×
258

259
// Update doesn't need to change a list visual.
260
func (l *ListSegment) Update(fyne.CanvasObject) {
×
261
}
×
262

263
// Select does nothing for a list container.
264
func (l *ListSegment) Select(_, _ fyne.Position) {
×
265
}
×
266

267
// SelectedText returns the empty string for this list.
268
func (l *ListSegment) SelectedText() string {
×
269
        return ""
×
270
}
×
271

272
// Unselect does nothing for a list container.
273
func (l *ListSegment) Unselect() {
×
274
}
×
275

276
// ParagraphSegment wraps a number of text elements in a paragraph.
277
// It is similar to using a list of text elements when the final style is RichTextStyleParagraph.
278
//
279
// Since: 2.1
280
type ParagraphSegment struct {
281
        Texts []RichTextSegment
282
}
283

284
// Inline returns false as a paragraph should be in a block.
285
func (p *ParagraphSegment) Inline() bool {
5✔
286
        return false
5✔
287
}
5✔
288

289
// Segments returns the list of text elements in this paragraph.
290
func (p *ParagraphSegment) Segments() []RichTextSegment {
8✔
291
        return p.Texts
8✔
292
}
8✔
293

294
// Textual returns no content for a paragraph container.
295
func (p *ParagraphSegment) Textual() string {
×
296
        return ""
×
297
}
×
298

299
// Visual returns the no extra elements.
300
func (p *ParagraphSegment) Visual() fyne.CanvasObject {
×
301
        return nil
×
302
}
×
303

304
// Update doesn't need to change a paragraph container.
305
func (p *ParagraphSegment) Update(fyne.CanvasObject) {
×
306
}
×
307

308
// Select does nothing for a paragraph container.
309
func (p *ParagraphSegment) Select(_, _ fyne.Position) {
×
310
}
×
311

312
// SelectedText returns the empty string for this paragraph container.
313
func (p *ParagraphSegment) SelectedText() string {
×
314
        return ""
×
315
}
×
316

317
// Unselect does nothing for a paragraph container.
318
func (p *ParagraphSegment) Unselect() {
×
319
}
×
320

321
// SeparatorSegment includes a horizontal separator in a rich text widget.
322
//
323
// Since: 2.1
324
type SeparatorSegment struct {
325
        _ bool // Without this a pointer to SeparatorSegment will always be the same.
326
}
327

328
// Inline returns false as a separator should be full width.
UNCOV
329
func (s *SeparatorSegment) Inline() bool {
×
UNCOV
330
        return false
×
UNCOV
331
}
×
332

333
// Textual returns no content for a separator element.
334
func (s *SeparatorSegment) Textual() string {
×
335
        return ""
×
336
}
×
337

338
// Visual returns a new instance of a separator widget for this segment.
UNCOV
339
func (s *SeparatorSegment) Visual() fyne.CanvasObject {
×
UNCOV
340
        return NewSeparator()
×
UNCOV
341
}
×
342

343
// Update doesn't need to change a separator visual.
344
func (s *SeparatorSegment) Update(fyne.CanvasObject) {
×
345
}
×
346

347
// Select does nothing for a separator.
348
func (s *SeparatorSegment) Select(_, _ fyne.Position) {
×
349
}
×
350

351
// SelectedText returns the empty string for this separator.
352
func (s *SeparatorSegment) SelectedText() string {
×
353
        return "" // TODO maybe return "---\n"?
×
354
}
×
355

356
// Unselect does nothing for a separator.
357
func (s *SeparatorSegment) Unselect() {
×
358
}
×
359

360
// RichTextStyle describes the details of a text object inside a RichText widget.
361
//
362
// Since: 2.1
363
type RichTextStyle struct {
364
        Alignment fyne.TextAlign
365
        ColorName fyne.ThemeColorName
366
        Inline    bool
367
        SizeName  fyne.ThemeSizeName // The theme name of the text size to use, if blank will be the standard text size
368
        TextStyle fyne.TextStyle
369

370
        // an internal detail where we obscure password fields
371
        concealed bool
372
}
373

374
// RichTextSegment describes any element that can be rendered in a RichText widget.
375
//
376
// Since: 2.1
377
type RichTextSegment interface {
378
        Inline() bool
379
        Textual() string
380
        Update(fyne.CanvasObject)
381
        Visual() fyne.CanvasObject
382

383
        Select(pos1, pos2 fyne.Position)
384
        SelectedText() string
385
        Unselect()
386
}
387

388
// TextSegment represents the styling for a segment of rich text.
389
//
390
// Since: 2.1
391
type TextSegment struct {
392
        Style RichTextStyle
393
        Text  string
394

395
        parent *RichText
396
}
397

398
// Inline should return true if this text can be included within other elements, or false if it creates a new block.
399
func (t *TextSegment) Inline() bool {
33,118✔
400
        return t.Style.Inline
33,118✔
401
}
33,118✔
402

403
// Textual returns the content of this segment rendered to plain text.
404
func (t *TextSegment) Textual() string {
4,413✔
405
        return t.Text
4,413✔
406
}
4,413✔
407

408
// Visual returns a new instance of a graphical element required to render this segment.
409
func (t *TextSegment) Visual() fyne.CanvasObject {
1,922✔
410
        obj := canvas.NewText(t.Text, t.color())
1,922✔
411

1,922✔
412
        t.Update(obj)
1,922✔
413
        return obj
1,922✔
414
}
1,922✔
415

416
// Update applies the current state of this text segment to an existing visual.
417
func (t *TextSegment) Update(o fyne.CanvasObject) {
8,079✔
418
        obj := o.(*canvas.Text)
8,079✔
419
        obj.Text = t.Text
8,079✔
420
        obj.Color = t.color()
8,079✔
421
        obj.Alignment = t.Style.Alignment
8,079✔
422
        obj.TextStyle = t.Style.TextStyle
8,079✔
423
        obj.TextSize = t.size()
8,079✔
424
        obj.Refresh()
8,079✔
425
}
8,079✔
426

427
// Select tells the segment that the user is selecting the content between the two positions.
428
func (t *TextSegment) Select(begin, end fyne.Position) {
×
429
        // no-op: this will be added when we progress to editor
×
430
}
×
431

432
// SelectedText should return the text representation of any content currently selected through the Select call.
433
func (t *TextSegment) SelectedText() string {
×
434
        // no-op: this will be added when we progress to editor
×
435
        return ""
×
436
}
×
437

438
// Unselect tells the segment that the user is has cancelled the previous selection.
439
func (t *TextSegment) Unselect() {
×
440
        // no-op: this will be added when we progress to editor
×
441
}
×
442

443
func (t *TextSegment) color() color.Color {
10,001✔
444
        if t.Style.ColorName != "" {
19,959✔
445
                return theme.ColorForWidget(t.Style.ColorName, t.parent)
9,958✔
446
        }
9,958✔
447

448
        return theme.ColorForWidget(theme.ColorNameForeground, t.parent)
43✔
449
}
450

451
func (t *TextSegment) size() float32 {
44,730✔
452
        if t.Style.SizeName != "" {
89,415✔
453
                i := theme.SizeForWidget(t.Style.SizeName, t.parent)
44,685✔
454
                return i
44,685✔
455
        }
44,685✔
456

457
        i := theme.SizeForWidget(theme.SizeNameText, t.parent)
45✔
458
        return i
45✔
459
}
460

461
type richImage struct {
462
        BaseWidget
463
        align  fyne.TextAlign
464
        img    *canvas.Image
465
        oldMin fyne.Size
466
        layout *fyne.Container
467
        min    fyne.Size
468
}
469

470
func newRichImage(u fyne.URI, align fyne.TextAlign) *richImage {
1✔
471
        img := canvas.NewImageFromURI(u)
1✔
472
        img.FillMode = canvas.ImageFillOriginal
1✔
473
        i := &richImage{img: img, align: align}
1✔
474
        i.ExtendBaseWidget(i)
1✔
475
        return i
1✔
476
}
1✔
477

478
func (r *richImage) CreateRenderer() fyne.WidgetRenderer {
1✔
479
        r.layout = &fyne.Container{Layout: &richImageLayout{r}, Objects: []fyne.CanvasObject{r.img}}
1✔
480
        return NewSimpleRenderer(r.layout)
1✔
481
}
1✔
482

483
func (r *richImage) MinSize() fyne.Size {
9✔
484
        orig := r.img.MinSize()
9✔
485
        c := fyne.CurrentApp().Driver().CanvasForObject(r)
9✔
486
        if c == nil {
9✔
487
                return r.oldMin // not yet rendered
×
488
        }
×
489

490
        // unscale the image so it is not varying based on canvas
491
        w := scale.ToScreenCoordinate(c, orig.Width)
9✔
492
        h := scale.ToScreenCoordinate(c, orig.Height)
9✔
493
        // we return size / 2 as this assumes a HiDPI / 2x image scaling
9✔
494
        r.min = fyne.NewSize(float32(w)/2, float32(h)/2)
9✔
495
        return r.min
9✔
496
}
497

498
func (r *richImage) setAlign(a fyne.TextAlign) {
4✔
499
        if r.layout != nil {
7✔
500
                r.layout.Refresh()
3✔
501
        }
3✔
502
        r.align = a
4✔
503
}
504

505
type richImageLayout struct {
506
        r *richImage
507
}
508

509
func (r *richImageLayout) Layout(_ []fyne.CanvasObject, s fyne.Size) {
9✔
510
        r.r.img.Resize(r.r.min)
9✔
511
        gap := float32(0)
9✔
512

9✔
513
        switch r.r.align {
9✔
514
        case fyne.TextAlignCenter:
2✔
515
                gap = (s.Width - r.r.min.Width) / 2
2✔
516
        case fyne.TextAlignTrailing:
1✔
517
                gap = s.Width - r.r.min.Width
1✔
518
        }
519

520
        r.r.img.Move(fyne.NewPos(gap, 0))
9✔
521
}
522

523
func (r *richImageLayout) MinSize(_ []fyne.CanvasObject) fyne.Size {
×
524
        return r.r.min
×
525
}
×
526

527
type unpadTextWidgetLayout struct {
528
        parent fyne.Widget
529
}
530

531
func (u *unpadTextWidgetLayout) Layout(o []fyne.CanvasObject, s fyne.Size) {
4✔
532
        innerPad := theme.SizeForWidget(theme.SizeNameInnerPadding, u.parent)
4✔
533
        pad := innerPad * -1
4✔
534
        pad2 := pad * -2
4✔
535

4✔
536
        o[0].Move(fyne.NewPos(pad, pad))
4✔
537
        o[0].Resize(s.Add(fyne.NewSize(pad2, pad2)))
4✔
538
}
4✔
539

540
func (u *unpadTextWidgetLayout) MinSize(o []fyne.CanvasObject) fyne.Size {
10✔
541
        innerPad := theme.SizeForWidget(theme.SizeNameInnerPadding, u.parent)
10✔
542
        pad := innerPad * 2
10✔
543
        return o[0].MinSize().Subtract(fyne.NewSize(pad, pad))
10✔
544
}
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc