~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?
No worries! I was new too not so long ago. You can read a little about signing off commits here: https://stackoverflow.com/a/1962112 It can be done by supplying the `-s` flag to `git commit`. It's basically a way of explicitly stating that you agree to the license of the project you're sending the patches to, and that you have the legal right to submit the patches (the legal right part is more relevant for corporate contributions).
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'm happy to answer questions about this. :D Please don't let this hold you back.
I just wanted richtext to be in gio-x. Sourcehut is a bit more complicated as github/gitlab :/
Sourcehut is certainly different. If you'd rather submit your work as a GitHub Pull Request, you can do that to our mirror repos hosted in this Org: https://github.com/gioui/ Cheers, Chris
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?
If you use the `-s` flag like (`git commit -s --amend` to just sign off the commit that already exists), it will be correct. :D The rest looks good. Thanks, Chris
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 -3Learn more about email & git
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
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