From: Vladimir Magyar <magyarv@protonmail.com>
---
richtext/example/main.go | 106 +++++++++++++++++++++
richtext/richtext.go | 195 +++++++++++++++++++++++++++++++++++++++
2 files changed, 301 insertions(+)
create mode 100644 richtext/example/main.go
create mode 100644 richtext/richtext.go
diff --git a/richtext/example/main.go b/richtext/example/main.go
new file mode 100644
index 0000000..3c8df32
--- /dev/null
+++ b/richtext/example/main.go
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package main
+
+// A simple Gio program. See https://gioui.org for more information.
+
+import (
+ "image/color"
+ "log"
+ "os"
+
+ "gioui.org/app"
+ "gioui.org/font/gofont"
+ "gioui.org/io/system"
+ "gioui.org/layout"
+ "gioui.org/op"
+ "gioui.org/text"
+ "gioui.org/unit"
+ "gioui.org/widget/material"
+ "gitlab.com/microo8/richtext"
+)
+
+var (
+ fontCollection = gofont.Collection()
+ shaper = text.NewCache(fontCollection)
+ th = material.NewTheme(fontCollection)
+ rt = richtext.TextObjects{
+ {
+ Font: fontCollection[1].Font,
+ Color: color.NRGBA{R: 0, G: 255, B: 0, A: 255},
+ Size: unit.Dp(25),
+ Content: "Vivamus integer non suscipit taciti mus etiam at primis tempor sagittis sit, euismod libero facilisi aptent elementum felis blandit cursus gravida sociis erat ante, eleifend lectus nullam dapibus netus feugiat curae curabitur est ad ",
+ },
+ {
+ Font: fontCollection[0].Font,
+ Color: color.NRGBA{R: 0, G: 0, B: 0, A: 255},
+ Size: unit.Dp(15),
+ Content: "Mollis pretium lorem primis senectus habitasse lectus scelerisque donec, ultricies tortor ",
+ },
+ {
+ Font: fontCollection[2].Font,
+ Color: color.NRGBA{R: 255, G: 0, B: 0, A: 255},
+ Size: unit.Dp(41),
+ Clickable: true,
+ Content: "A VERY VERY VERY VERY VERY VERY VERY VERY LONG CLICKABLE TEXT OBJECT",
+ },
+ {
+ Font: fontCollection[4].Font,
+ Color: color.NRGBA{R: 0, G: 0, B: 255, A: 255},
+ Size: unit.Dp(20),
+ Content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec volutpat ex a massa hendrerit sodales. Maecenas eget ullamcorper nulla. Suspendisse tincidunt sapien sit amet lorem tincidunt.
+
+Sed mattis hendrerit ex. Curabitur dignissim ante turpis, ut euismod purus consequat ut. Etiam eget vulputate nibh. Ut sollicitudin ipsum volutpat leo tincidunt, et suscipit purus malesuada. Cras pulvinar lectus id ipsum congue, pretium mattis tellus auctor. Nunc faucibus interdum risus, at maximus ex pellentesque non. Cras lectus ex, ullamcorper eu felis ut, suscipit placerat sem. Vestibulum ex libero, lobortis et consectetur eu, semper vitae mauris. Pellentesque cursus massa in efficitur porta.`,
+ },
+ {
+ Font: fontCollection[2].Font,
+ Color: color.NRGBA{R: 255, G: 0, B: 0, A: 255},
+ Size: unit.Dp(44),
+ Clickable: true,
+ Content: "ANOTHER VERY VERY VERY VERY VERY VERY VERY VERY LONG CLICKABLE TEXT OBJECT",
+ },
+ {
+ Font: fontCollection[0].Font,
+ Color: color.NRGBA{R: 0, G: 0, B: 0, A: 255},
+ Size: unit.Dp(22),
+ Content: "Mollis pretium lorem primis senectus habitasse lectus scelerisque donec, ultricies tortor ",
+ },
+ {
+ Font: fontCollection[2].Font,
+ Color: color.NRGBA{R: 255, G: 0, B: 0, A: 255},
+ Size: unit.Dp(32),
+ Clickable: true,
+ Content: "CLICKABLE",
+ },
+ }
+)
+
+func main() {
+ log.SetFlags(log.Lshortfile | log.LstdFlags)
+ go func() {
+ w := app.NewWindow()
+ if err := loop(w); err != nil {
+ log.Fatal(err)
+ }
+ os.Exit(0)
+ }()
+ app.Main()
+}
+
+func loop(w *app.Window) error {
+ var ops op.Ops
+ for {
+ e := <-w.Events()
+ switch e := e.(type) {
+ case system.DestroyEvent:
+ return e.Err
+ case system.FrameEvent:
+ gtx := layout.NewContext(&ops, e)
+ if obj := rt.Clicked(); obj != nil {
+ log.Println(obj)
+ }
+ rt.Layout(gtx, shaper)
+ e.Frame(gtx.Ops)
+ }
+ }
+}
diff --git a/richtext/richtext.go b/richtext/richtext.go
new file mode 100644
index 0000000..e8fff84
--- /dev/null
+++ b/richtext/richtext.go
@@ -0,0 +1,195 @@
+package richtext
+
+import (
+ "image"
+ "image/color"
+
+ "gioui.org/io/pointer"
+ "gioui.org/layout"
+ "gioui.org/op"
+ "gioui.org/op/paint"
+ "gioui.org/text"
+ "gioui.org/unit"
+ "golang.org/x/image/math/fixed"
+)
+
+//TextObjects represents the whole richtext widget
+type TextObjects []*TextObject
+
+//TextObject is one of the objects in the richtext widget
+type TextObject struct {
+ Font text.Font
+ Size unit.Value
+ Color color.NRGBA
+ Content string
+ Clickable bool
+ metadata map[string]string
+
+ origin *TextObject
+ textLayout text.Layout
+ xOffset int
+ size image.Point
+ clicked int
+}
+
+//Layout prints out the text at specified offset
+func (to *TextObject) Layout(gtx layout.Context, s text.Shaper, off image.Point) layout.Dimensions {
+ stack := op.Save(gtx.Ops)
+ paint.ColorOp{Color: to.Color}.Add(gtx.Ops)
+ op.Offset(layout.FPt(off)).Add(gtx.Ops)
+ s.Shape(to.Font, fixed.I(gtx.Px(to.Size)), to.textLayout).Add(gtx.Ops)
+ paint.PaintOp{}.Add(gtx.Ops)
+ stack.Load()
+ return layout.Dimensions{Size: to.size}
+}
+
+func (to *TextObject) Clicked() bool {
+ if to.clicked <= 0 {
+ return false
+ }
+ to.clicked--
+ return true
+}
+
+func (to *TextObject) updateClicks(gtx layout.Context) {
+ if !to.Clickable {
+ return
+ }
+ for _, e := range gtx.Events(origin(to)) {
+ switch p := e.(type) {
+ case pointer.Event:
+ switch p.Type {
+ case pointer.Release:
+ to.clicked++
+ }
+ }
+ }
+}
+
+func (to *TextObject) SetMetadata(key, value string) {
+ if to.metadata == nil {
+ to.metadata = make(map[string]string)
+ }
+ to.metadata[key] = value
+}
+
+func (to *TextObject) GetMetadata(key string) string {
+ if to.metadata == nil {
+ return ""
+ }
+ return to.metadata[key]
+}
+
+//Layout prints out the rich text widget
+func (tos TextObjects) Layout(gtx layout.Context, s text.Shaper) layout.Dimensions {
+ var tosDims layout.Dimensions
+ var lineDims image.Point
+ oi := &objectIterator{objs: tos}
+ //TextObjects (or it's parts) on a single line
+ var lineObjects []*TextObject
+
+ for to := oi.Next(); to != nil; to = oi.Next() {
+ maxWidth := gtx.Constraints.Max.X - lineDims.X
+ //layout the string using the maxWidthe remaining for the line
+ lines := s.LayoutString(to.Font, fixed.I(gtx.Px(to.Size)), maxWidth, to.Content)
+ //using just the first line
+ first := lines[0]
+ lineWidth := first.Width.Ceil()
+ lineHeight := (first.Ascent + first.Descent).Ceil()
+
+ //getting the used text in the line and calculating the dimensions
+ to.textLayout = first.Layout
+ to.xOffset = lineDims.X
+ to.size = image.Point{X: lineWidth, Y: lineHeight}
+ lineObjects = append(lineObjects, to)
+
+ //calculating the X and the max of the Y of the line
+ lineDims.X += lineWidth
+ if lineDims.Y < lineHeight {
+ lineDims.Y = lineHeight
+ }
+ //the width of the whole richtext object
+ if tosDims.Size.X < lineDims.X {
+ tosDims.Size.X = lineDims.X
+ }
+
+ //if we are going to break the line, or we are at the end of the richtext
+ if len(lines) > 1 || oi.Empty() {
+ //we print the line
+ for _, obj := range lineObjects {
+ off := image.Point{
+ X: obj.xOffset,
+ Y: tosDims.Size.Y + lineDims.Y,
+ }
+ obj.Layout(gtx, s, off)
+ if !obj.Clickable {
+ continue
+ }
+ to.updateClicks(gtx)
+ stack := op.Save(gtx.Ops)
+ clickableOffset := image.Point{X: obj.xOffset, Y: tosDims.Size.Y}
+ op.Offset(layout.FPt(clickableOffset)).Add(gtx.Ops)
+ pointer.Rect(image.Rectangle{Max: obj.size}).Add(gtx.Ops)
+ pointer.InputOp{Tag: origin(obj), Types: pointer.Release}.Add(gtx.Ops)
+ pointer.CursorNameOp{Name: pointer.CursorPointer}.Add(gtx.Ops)
+ stack.Load()
+ }
+ //we printed these objectss, so we get rid of them
+ lineObjects = nil
+ tosDims.Size.Y += lineDims.Y
+ }
+ if len(lines) > 1 {
+ //add the rest of the TextObject to be printed on a new line
+ //this puts it to the front of the objectIterator
+ oi.Add(&TextObject{
+ origin: origin(to),
+ Font: to.Font,
+ Size: to.Size,
+ Color: to.Color,
+ Clickable: to.Clickable,
+ Content: to.Content[len(first.Layout.Text):],
+ })
+ lineDims.X = 0
+ lineDims.Y = 0
+ }
+ }
+
+ return tosDims
+}
+
+type objectIterator struct {
+ objs []*TextObject
+}
+
+func (oi *objectIterator) Add(to *TextObject) {
+ oi.objs = append([]*TextObject{to}, oi.objs...)
+}
+
+func (oi *objectIterator) Next() *TextObject {
+ if len(oi.objs) == 0 {
+ return nil
+ }
+ next := oi.objs[0]
+ oi.objs = oi.objs[1:]
+ return next
+}
+
+func (oi *objectIterator) Empty() bool {
+ return len(oi.objs) == 0
+}
+
+func (tos TextObjects) Clicked() *TextObject {
+ for _, to := range tos {
+ if to.Clicked() {
+ return to
+ }
+ }
+ return nil
+}
+
+func origin(to *TextObject) *TextObject {
+ if to.origin == nil {
+ return to
+ }
+ return to.origin
+}
--
2.30.2
From: Vladimir Magyar <magyarv@protonmail.com>
---
README.md | 23 ++++++-------
richtext/README.md | 70 ++++++++++++++++++++++++++++++++++++++++
richtext/example/main.go | 4 +--
3 files changed, 83 insertions(+), 14 deletions(-)
create mode 100644 richtext/README.md
diff --git a/README.md b/README.md
index 8b00352..3b14d20 100644
--- a/README.md
+++ b/README.md
@@ -6,17 +6,18 @@ This repository hosts `gioui.org/x`. Two kinds of package exist in this namespac
This table describes the current status of each package in `gioui.org/x`:
-| Name | Purpose | Intended for core? | Non-core dependencies? | API Stability |
-| --- | --- | --- | --- | --- |
-| colorpicker | Widgets for choosing colors | uncertain | no | unstable |
-| component | Material.io components | uncertain | no | unstable |
-| eventx | Event management tools | yes | no | unstable |
-| haptic | Haptic feedback for mobile devices | no | yes | unstable |
-| notify | Background notifications | no | yes | unstable |
-| outlay | Extra layouts | yes | no | unstable |
-| pref | Query user/device preferences | no | yes | unstable |
-| profiling | Gio render performance recording tools | uncertain | no | unstable |
-| scroll | Scrollbar widget for Gio | yes | no | unstable |
+| Name | Purpose | Intended for core? | Non-core dependencies? | API Stability |
+| ----------- | ------------------------------------------- | ------------------ | ---------------------- | ------------- |
+| colorpicker | Widgets for choosing colors | uncertain | no | unstable |
+| component | Material.io components | uncertain | no | unstable |
+| eventx | Event management tools | yes | no | unstable |
+| haptic | Haptic feedback for mobile devices | no | yes | unstable |
+| notify | Background notifications | no | yes | unstable |
+| outlay | Extra layouts | yes | no | unstable |
+| pref | Query user/device preferences | no | yes | unstable |
+| profiling | Gio render performance recording tools | uncertain | no | unstable |
+| scroll | Scrollbar widget for Gio | yes | no | unstable |
+| richtext | Printing text objects with different styles | uncertain | no | unstable |
## Contributing
diff --git a/richtext/README.md b/richtext/README.md
new file mode 100644
index 0000000..d089bc8
--- /dev/null
+++ b/richtext/README.md
@@ -0,0 +1,70 @@
+# richtext
+
+Provides a widget that renders text in different styles.
+
+```
+var (
+ fontCollection = gofont.Collection()
+ shaper = text.NewCache(fontCollection)
+ rt = richtext.TextObjects{
+ {
+ Font: fontCollection[1].Font,
+ Color: color.NRGBA{R: 0, G: 255, B: 0, A: 255},
+ Size: unit.Dp(25),
+ Content: "Vivamus integer non suscipit taciti mus etiam at primis tempor sagittis sit, euismod libero facilisi aptent elementum felis blandit cursus gravida sociis erat ante, eleifend lectus nullam dapibus netus feugiat curae curabitur est ad ",
+ },
+ {
+ Font: fontCollection[2].Font,
+ Color: color.NRGBA{R: 255, G: 0, B: 0, A: 255},
+ Size: unit.Dp(32),
+ Clickable: true,
+ Content: "CLICKABLE",
+ },
+ {
+ Font: fontCollection[4].Font,
+ Color: color.NRGBA{R: 0, G: 0, B: 255, A: 255},
+ Size: unit.Dp(20),
+ Content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec volutpat ex a massa hendrerit sodales. Maecenas eget ullamcorper nulla. Suspendisse tincidunt sapien sit amet lorem tincidunt.
+
+Sed mattis hendrerit ex. Curabitur dignissim ante turpis, ut euismod purus consequat ut. Etiam eget vulputate nibh. Ut sollicitudin ipsum volutpat leo tincidunt, et suscipit purus malesuada. Cras pulvinar lectus id ipsum congue, pretium mattis tellus auctor. Nunc faucibus interdum risus, at maximus ex pellentesque non. Cras lectus ex, ullamcorper eu felis ut, suscipit placerat sem. Vestibulum ex libero, lobortis et consectetur eu, semper vitae mauris. Pellentesque cursus massa in efficitur porta.`,
+ },
+ {
+ Font: fontCollection[0].Font,
+ Color: color.NRGBA{R: 0, G: 0, B: 0, A: 255},
+ Size: unit.Dp(22),
+ Content: "Mollis pretium lorem primis senectus habitasse lectus scelerisque donec, ultricies tortor ",
+ },
+ }
+)
+
+func main() {
+ log.SetFlags(log.Lshortfile | log.LstdFlags)
+ go func() {
+ w := app.NewWindow()
+ if err := loop(w); err != nil {
+ log.Fatal(err)
+ }
+ os.Exit(0)
+ }()
+ app.Main()
+}
+
+func loop(w *app.Window) error {
+ var ops op.Ops
+ for {
+ e := <-w.Events()
+ switch e := e.(type) {
+ case system.DestroyEvent:
+ return e.Err
+ case system.FrameEvent:
+ gtx := layout.NewContext(&ops, e)
+ //get the clicked TextObject
+ if obj := rt.Clicked(); obj != nil {
+ log.Println(obj)
+ }
+ rt.Layout(gtx, shaper)
+ e.Frame(gtx.Ops)
+ }
+ }
+}
+```
diff --git a/richtext/example/main.go b/richtext/example/main.go
index 3c8df32..ea0dd0d 100644
--- a/richtext/example/main.go
+++ b/richtext/example/main.go
@@ -16,14 +16,12 @@ import (
"gioui.org/op"
"gioui.org/text"
"gioui.org/unit"
- "gioui.org/widget/material"
- "gitlab.com/microo8/richtext"
+ "gioui.org/x/richtext"
)
var (
fontCollection = gofont.Collection()
shaper = text.NewCache(fontCollection)
- th = material.NewTheme(fontCollection)
rt = richtext.TextObjects{
{
Font: fontCollection[1].Font,
--
2.30.2