~eliasnaur/gio

gio-x: Add richtext v1 PROPOSED

~ghost08: 2
 Add richtext
 Add richtext

 5 files changed, 384 insertions(+), 14 deletions(-)
Sorry I'm new to this patchsets, git send-email and what is "sign off" the commit?
I would like to learn this, but don't want to rob you of your time.
Maybe I study this first and then submit my work.
I just wanted richtext to be in gio-x. Sourcehut is a bit more complicated as github/gitlab :/



          
          
          
        
      

      
      
      
      

      

      
      
      
      

      
      
        
          






Ok thanks, so this (https://lists.sr.ht/~eliasnaur/gio-patches/patches/22954)
patch isn't correct because I didn't use the `-s` flag?

If I do it with the 'sign off' it will be ok?
Or did I forget about something?



          
          
          
        
      

      
      
      
      

      

      
      
      
      
    
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio/patches/22950/mbox | git am -3
Learn more about email & git

[PATCH gio-x 1/2] Add richtext Export this patch

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
Hi Vladimir,

Thanks for the patches! I see that you've got it working using the raw
pointer operations, and that seems to work quite nicely. Well done.

I have a few requests:
- Can you send future patches to the gio-patches mailing list? We
decided to create a separate list for patches so that people who are
only interested in announcements don't get bombarded with email. I'm
sorry that I didn't mention this before when I asked you to send a
patch. That's my mistake. You can find that mailing list here:
https://lists.sr.ht/~eliasnaur/gio-patches
- Can we leave the example program out of this patch series? The
example really belongs in gioui.org/example, which is in a separate
repo. Can you send the example as a patch to gio-example, and put the
code at the path ./x/richtext within that repo? The gio-example repo
is here: https://git.sr.ht/~eliasnaur/gio-example
- Can you sign-off the commits? I think that you could reasonably make
this whole change a single commit, so one way to achieve that would be
to:
    - git rebase -i HEAD^^
    - mark the last commit as `fixup`, save and quit the file
    - git commit -s --amend
    - These steps combine your two commits into one, and then sign off
that one commit.
    - Please note that signing off your commit indicates that you
agree to license the code under the dual MIT/Unlicense, same as the
rest of the repo. I encourage you to read about the unlicense if you
aren't already familiar with it.

Thanks,
Chris

[PATCH gio-x 2/2] Add richtext Export this patch

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