Skip to content

Commit 6c32cec

Browse files
committed
Edit tags as normal text, not as tag-badges
1 parent e0df5bf commit 6c32cec

File tree

8 files changed

+376
-262
lines changed

8 files changed

+376
-262
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ require (
303303
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241018162120-5faf4e7a684a
304304
github.com/xaionaro-go/observability v0.0.0-20250420133500-5c4d2e045932
305305
github.com/xaionaro-go/player v0.0.0-20250427220051-e366ad8a1fb5
306-
github.com/xaionaro-go/recoder v0.0.0-20250428012439-3fc4b32e59a9
306+
github.com/xaionaro-go/recoder v0.0.0-20250503155018-6f353978d332
307307
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2
308308
github.com/xaionaro-go/serializable v0.0.0-20250412140540-5ac572306599
309309
github.com/xaionaro-go/timeapiio v0.0.0-20240915203246-b907cf699af3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,8 @@ github.com/xaionaro-go/pulse v0.0.0-20241023202712-7151fa00d4bb h1:9iHPI27CYbmJD
11631163
github.com/xaionaro-go/pulse v0.0.0-20241023202712-7151fa00d4bb/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
11641164
github.com/xaionaro-go/recoder v0.0.0-20250428012439-3fc4b32e59a9 h1:744Nrf5lfHG0lOpivgXHDDsENer25zJlr/f5puIWhGs=
11651165
github.com/xaionaro-go/recoder v0.0.0-20250428012439-3fc4b32e59a9/go.mod h1:Twc+NcQQ+afg4RHxwqqo9pRGIaY7+QwpuAiYa7ClSLw=
1166+
github.com/xaionaro-go/recoder v0.0.0-20250503155018-6f353978d332 h1:jB5I8UE9UL6g7qQKaZ/g9wt3lIXxgkDDJX1cV37D5go=
1167+
github.com/xaionaro-go/recoder v0.0.0-20250503155018-6f353978d332/go.mod h1:Twc+NcQQ+afg4RHxwqqo9pRGIaY7+QwpuAiYa7ClSLw=
11661168
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2 h1:QHpTWfyfmz65cE0MtFXe9fScdi+X0VIYR2wgolSYEUk=
11671169
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2/go.mod h1:XKoHGZ4VKMbVBl8VotLIoWQdrB6Q7jnR++RbkiegZFU=
11681170
github.com/xaionaro-go/serializable v0.0.0-20250412140540-5ac572306599 h1:CzcQd6wLiqgjd8K/6UzR5uyt6sg4ut/kVxi6+FJMbdI=

pkg/streampanel/assert.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package streampanel
2+
3+
func assert(b bool) {
4+
if !b {
5+
panic("assertion failed")
6+
}
7+
}

pkg/streampanel/disabled/tags.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package streampanel
2+
3+
import (
4+
"strings"
5+
6+
"fyne.io/fyne/v2"
7+
"fyne.io/fyne/v2/container"
8+
"fyne.io/fyne/v2/theme"
9+
"fyne.io/fyne/v2/widget"
10+
)
11+
12+
func newTagsEditor(
13+
initialTags []string,
14+
tagCountLimit uint,
15+
additionalButtons ...tagEditButton,
16+
) *tagsEditorSection {
17+
t := &tagsEditorSection{}
18+
tagsEntryField := widget.NewEntry()
19+
tagsEntryField.SetPlaceHolder("add a tag")
20+
s := tagsEntryField.Size()
21+
s.Width = 200
22+
tagsMap := map[string]struct{}{}
23+
tagsEntryField.Resize(s)
24+
tagsControlsContainer := container.NewHBox()
25+
t.tagsContainer = container.NewGridWrap(fyne.NewSize(200, 30))
26+
selectedTags := map[string]struct{}{}
27+
selectedTagsOrdered := func() []tagInfo {
28+
var result []tagInfo
29+
for _, tag := range t.Tags {
30+
if _, ok := selectedTags[tag.Tag]; ok {
31+
result = append(result, tag)
32+
}
33+
}
34+
return result
35+
}
36+
37+
tagContainerToFirstButton := widget.NewButtonWithIcon("", theme.MediaFastRewindIcon(), func() {
38+
for _, tag := range selectedTagsOrdered() {
39+
idx := t.getIdx(tag)
40+
if idx < 1 {
41+
return
42+
}
43+
t.move(idx, 0)
44+
}
45+
})
46+
tagsControlsContainer.Add(tagContainerToFirstButton)
47+
48+
tagContainerToPrevButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() {
49+
for _, tag := range selectedTagsOrdered() {
50+
idx := t.getIdx(tag)
51+
if idx < 1 {
52+
return
53+
}
54+
t.move(idx, idx-1)
55+
}
56+
})
57+
tagsControlsContainer.Add(tagContainerToPrevButton)
58+
tagContainerToNextButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
59+
for _, tag := range reverse(selectedTagsOrdered()) {
60+
idx := t.getIdx(tag)
61+
if idx >= len(t.Tags)-1 {
62+
return
63+
}
64+
t.move(idx, idx+2)
65+
}
66+
})
67+
tagsControlsContainer.Add(tagContainerToNextButton)
68+
tagContainerToLastButton := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() {
69+
for _, tag := range reverse(selectedTagsOrdered()) {
70+
idx := t.getIdx(tag)
71+
if idx >= len(t.Tags)-1 {
72+
return
73+
}
74+
t.move(idx, len(t.Tags))
75+
}
76+
})
77+
tagsControlsContainer.Add(tagContainerToLastButton)
78+
79+
removeTag := func(tag string) {
80+
tagInfo := t.getTagInfo(tag)
81+
t.tagsContainer.Remove(tagInfo.Container)
82+
delete(tagsMap, tag)
83+
for idx, tagCmp := range t.Tags {
84+
if tagCmp.Tag == tag {
85+
t.Tags = append(t.Tags[:idx], t.Tags[idx+1:]...)
86+
break
87+
}
88+
}
89+
}
90+
91+
tagsControlsContainer.Add(widget.NewSeparator())
92+
tagsControlsContainer.Add(widget.NewSeparator())
93+
tagsControlsContainer.Add(widget.NewSeparator())
94+
tagContainerRemoveButton := widget.NewButtonWithIcon("", theme.ContentClearIcon(), func() {
95+
for tag := range selectedTags {
96+
removeTag(tag)
97+
}
98+
})
99+
tagsControlsContainer.Add(tagContainerRemoveButton)
100+
101+
tagsControlsContainer.Add(widget.NewSeparator())
102+
tagsControlsContainer.Add(widget.NewSeparator())
103+
tagsControlsContainer.Add(widget.NewSeparator())
104+
for _, additionalButtonInfo := range additionalButtons {
105+
button := widget.NewButtonWithIcon(
106+
additionalButtonInfo.Label,
107+
additionalButtonInfo.Icon,
108+
func() {
109+
additionalButtonInfo.Callback(t, selectedTagsOrdered())
110+
},
111+
)
112+
tagsControlsContainer.Add(button)
113+
}
114+
115+
addTag := func(tagName string) {
116+
if tagCountLimit > 0 && len(t.Tags) >= int(tagCountLimit) {
117+
removeTag(t.Tags[tagCountLimit-1].Tag)
118+
}
119+
tagName = strings.Trim(tagName, " ")
120+
if tagName == "" {
121+
return
122+
}
123+
if _, ok := tagsMap[tagName]; ok {
124+
return
125+
}
126+
127+
tagsMap[tagName] = struct{}{}
128+
tagContainer := container.NewHBox()
129+
t.Tags = append(t.Tags, tagInfo{
130+
Tag: tagName,
131+
Container: tagContainer,
132+
})
133+
134+
tagLabel := tagName
135+
overflown := false
136+
for {
137+
size := fyne.MeasureText(
138+
tagLabel,
139+
fyne.CurrentApp().Settings().Theme().Size("text"),
140+
fyne.TextStyle{},
141+
)
142+
if size.Width < 100 {
143+
break
144+
}
145+
tagLabel = tagLabel[:len(tagLabel)-1]
146+
overflown = true
147+
}
148+
if overflown {
149+
tagLabel += "…"
150+
}
151+
tagSelector := widget.NewCheck(tagLabel, func(b bool) {
152+
if b {
153+
selectedTags[tagName] = struct{}{}
154+
} else {
155+
delete(selectedTags, tagName)
156+
}
157+
})
158+
tagContainer.Add(tagSelector)
159+
t.tagsContainer.Add(tagContainer)
160+
}
161+
tagsEntryField.OnSubmitted = func(text string) {
162+
for _, tag := range strings.Split(text, ",") {
163+
addTag(tag)
164+
}
165+
tagsEntryField.SetText("")
166+
}
167+
168+
for _, tag := range initialTags {
169+
addTag(tag)
170+
}
171+
t.CanvasObject = container.NewVBox(
172+
t.tagsContainer,
173+
tagsControlsContainer,
174+
tagsEntryField,
175+
)
176+
return t
177+
}
178+
179+
type tagEditButton struct {
180+
Icon fyne.Resource
181+
Label string
182+
Callback func(*tagsEditorSection, []tagInfo)
183+
}
184+
185+
type tagInfo struct {
186+
Tag string
187+
Container fyne.CanvasObject
188+
}
189+
190+
type tagsEditorSection struct {
191+
fyne.CanvasObject
192+
tagsContainer *fyne.Container
193+
Tags []tagInfo
194+
}
195+
196+
func (t *tagsEditorSection) getTagInfo(tag string) tagInfo {
197+
for _, tagCmp := range t.Tags {
198+
if tagCmp.Tag == tag {
199+
return tagCmp
200+
}
201+
}
202+
return tagInfo{}
203+
}
204+
205+
func (t *tagsEditorSection) getIdx(tag tagInfo) int {
206+
for idx, tagCmp := range t.Tags {
207+
if tagCmp.Tag == tag.Tag {
208+
return idx
209+
}
210+
}
211+
212+
return -1
213+
}
214+
215+
func (t *tagsEditorSection) move(srcIdx, dstIdx int) {
216+
newTags := make([]tagInfo, 0, len(t.Tags))
217+
newObjs := make([]fyne.CanvasObject, 0, len(t.Tags))
218+
219+
objs := t.tagsContainer.Objects
220+
for i := range t.Tags {
221+
if i == dstIdx {
222+
newTags = append(newTags, t.Tags[srcIdx])
223+
newObjs = append(newObjs, objs[srcIdx])
224+
}
225+
if i == srcIdx {
226+
continue
227+
}
228+
newTags = append(newTags, t.Tags[i])
229+
newObjs = append(newObjs, objs[i])
230+
}
231+
if dstIdx >= len(t.Tags) {
232+
newTags = append(newTags, t.Tags[srcIdx])
233+
newObjs = append(newObjs, objs[srcIdx])
234+
}
235+
236+
t.Tags = newTags
237+
t.tagsContainer.Objects = newObjs
238+
t.tagsContainer.Refresh()
239+
}
240+
241+
func (t *tagsEditorSection) GetTags() []string {
242+
result := make([]string, 0, len(t.Tags))
243+
for _, tag := range t.Tags {
244+
result = append(result, tag.Tag)
245+
}
246+
return result
247+
}

pkg/streampanel/util.go renamed to pkg/streampanel/disabled/util.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
package streampanel
22

3-
func assert(b bool) {
4-
if !b {
5-
panic("assertion failed")
6-
}
7-
}
8-
93
func reverse[T any](s []T) []T {
104
l := len(s)
11-
for i := 0; i < l/2; i++ {
5+
for i := range l / 2 {
126
s[i], s[l-1-i] = s[l-1-i], s[i]
137
}
148
return s

0 commit comments

Comments
 (0)