From 8592b1cc50c1bea9ab6f148933dd14ffd2e4b5c0 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 28 Sep 2024 19:29:01 +0200 Subject: [PATCH] menus --- README.md | 10 ++++-- example/main.go | 16 +++++++++ osui.go | 9 +---- ui/README.md | 37 ++++++++++++++++++++ ui/button.go | 30 ++++++++-------- ui/div.go | 43 ++++++++++++++++++++--- ui/inputBox.go | 10 +++--- ui/menu.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ ui/paginator.go | 4 +-- ui/text.go | 2 +- ui/utils.go | 16 ++++----- utils.go | 1 + 12 files changed, 222 insertions(+), 47 deletions(-) create mode 100644 ui/README.md create mode 100644 ui/menu.go diff --git a/README.md b/README.md index 2445e96..b1fc58a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,12 @@ import ( ) func main() { - components := osui.NewRender() - components.Add(ui.Text("Hello, World!")) - components.Render() + + app := ui.Div( + ui.Text("Hello, World!") + ) + + screen := osui.NewScreen(app) + screen.Run() } ``` \ No newline at end of file diff --git a/example/main.go b/example/main.go index b943ca8..126e2d5 100644 --- a/example/main.go +++ b/example/main.go @@ -13,6 +13,7 @@ func main() { func App() *PaginatorComponent { w, h := osui.GetTerminalSize() + text := Text("") return Paginator( // Page 1 Div( @@ -40,5 +41,20 @@ func App() *PaginatorComponent { WithPosition(0, 0, Text("This is a Button. To click it press Enter. "+colors.Red+colors.Bold+"NOTE: This Button is inside a div")), WithPosition((w-20)/2, (h-3)/2, Button("This is a button", BtnParams{OnClick: func(bc *ButtonComponent) bool { return false }})), ), + + // Page 5 + Div( + WithPosition(0, 2, Menu("Item 1", "Item 2", "Item 3").OnSelected(func(m *MenuComponent, b bool) { + if b { + text.Text = m.Items[m.SelectedItem] + } + })), + WithPosition(0, 6, Menu("Item A", "Item B", "Item C").OnSelected(func(m *MenuComponent, b bool) { + if b { + text.Text = m.Items[m.SelectedItem] + } + })), + text, + ).OnKey(func(d *DivComponent, k string) string { return "" }), ) } diff --git a/osui.go b/osui.go index f777f38..49bbe39 100644 --- a/osui.go +++ b/osui.go @@ -15,13 +15,6 @@ type ComponentData struct { DefaultColor string IsActive bool Screen *Screen - Update func(string) bool -} - -func NewComponent(c Component) Component { - data := c.GetComponentData() - data.Update = func(s string) bool { return c.Update(s) } - return c } type Component interface { @@ -71,7 +64,7 @@ func (s *Screen) Run() { for { s.Render() k, _ := ReadKey() - if data.Update(k) { + if s.component.Update(k) { ShowCursor() return } diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000..ab36921 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,37 @@ +# Component template (use rename symbol) + +```go +type HelloStyle struct { + Fg string `defaults:"" type:"fg"` +} + +type HelloComponent struct { + Data osui.ComponentData + Style *HelloStyle + Name string +} + +func (h *HelloComponent) GetComponentData() *osui.ComponentData { + return &h.Data +} + +func (h *HelloComponent) Render() string { + osui.UseStyle(h.Style) + return "Hello " + h.Style.Fg + h.Name + colors.Reset + "!" +} + +func (h *HelloComponent) Update(key string) bool { + return false +} + +func (h *HelloComponent) SetStyle(c interface{}) { + h.Style = osui.SetDefaults(c.(*HelloStyle)).(*HelloStyle) +} + +func Hello(name string) *HelloComponent { + return osui.NewComponent(&HelloComponent{ + Name: name, + Style: osui.SetDefaults(&HelloStyle{}).(*HelloStyle), + }).(*HelloComponent) +} +``` \ No newline at end of file diff --git a/ui/button.go b/ui/button.go index d694b3c..a62c92c 100644 --- a/ui/button.go +++ b/ui/button.go @@ -99,23 +99,21 @@ type BtnParams struct { func Button(text string, p ...BtnParams) *ButtonComponent { if len(p) > 0 { param := p[0] - return osui.NewComponent( - &ButtonComponent{Text: text, Style: osui.SetDefaults(¶m.Style).(*ButtonStyle), - Data: osui.ComponentData{ - Width: osui.LogicValueInt(param.Width == 0, 20, param.Width), - Height: 1, - }, - OnClick: fnLogicValue(param.OnClick), - }).(*ButtonComponent) - - } - return osui.NewComponent( - &ButtonComponent{Text: text, - Style: osui.SetDefaults(&ButtonStyle{}).(*ButtonStyle), + return &ButtonComponent{Text: text, Style: osui.SetDefaults(¶m.Style).(*ButtonStyle), Data: osui.ComponentData{ - Width: 20, + Width: osui.LogicValueInt(param.Width == 0, 20, param.Width), Height: 1, }, - OnClick: func(bc *ButtonComponent) bool { return false }, - }).(*ButtonComponent) + OnClick: fnLogicValue(param.OnClick), + } + + } + return &ButtonComponent{Text: text, + Style: osui.SetDefaults(&ButtonStyle{}).(*ButtonStyle), + Data: osui.ComponentData{ + Width: 20, + Height: 1, + }, + OnClick: func(bc *ButtonComponent) bool { return false }, + } } diff --git a/ui/div.go b/ui/div.go index eee35af..4c39068 100644 --- a/ui/div.go +++ b/ui/div.go @@ -9,8 +9,8 @@ import ( ) type DivStyle struct { - Background string `defaults:"" type:"bg"` - Foreground string `defaults:"" type:"fg"` + Background string `default:"" type:"bg"` + Foreground string `default:"" type:"fg"` } type DivComponent struct { @@ -18,6 +18,7 @@ type DivComponent struct { Style *DivStyle Components []osui.Component ActiveComponent int + onKey func(*DivComponent, string) string } func (d *DivComponent) GetComponentData() *osui.ComponentData { @@ -51,6 +52,30 @@ func (d *DivComponent) Render() string { } func (d *DivComponent) Update(key string) bool { + if d.onKey != nil { + o := d.onKey(d, key) + if o == "up" { + d.updateActive(d.ActiveComponent - 1) + } else if o == "down" { + d.updateActive(d.ActiveComponent + 1) + } else if o == "true" { + return true + } else if o == "false" { + return false + } else { + if len(d.Components) > 0 { + d.Components[d.ActiveComponent].GetComponentData().IsActive = d.Data.IsActive + if d.Components[d.ActiveComponent].Update(key) { + if d.ActiveComponent < len(d.Components)-1 { + d.updateActive(d.ActiveComponent + 1) + } else { + return true + } + } + } + } + return false + } if isKey.Up(key) { d.updateActive(d.ActiveComponent - 1) } else if isKey.Down(key) { @@ -80,9 +105,19 @@ func (d *DivComponent) updateActive(newIndex int) { } } +func (d *DivComponent) OnKey(f func(*DivComponent, string) string) *DivComponent { + d.onKey = f + return d +} + +func (d *DivComponent) AddTo(c *DivComponent) *DivComponent { + *c = *d + return d +} + func Div(components ...osui.Component) *DivComponent { - return osui.NewComponent(&DivComponent{ + return &DivComponent{ Components: components, Style: osui.SetDefaults(&DivStyle{}).(*DivStyle), - }).(*DivComponent) + } } diff --git a/ui/inputBox.go b/ui/inputBox.go index 522f735..cc2c4f4 100644 --- a/ui/inputBox.go +++ b/ui/inputBox.go @@ -10,10 +10,10 @@ import ( ) type InputBoxStyle struct { - Background string `defaults:"" type:"bg"` - Foreground string `defaults:"" type:"fg"` - Outline string `defaults:"" type:"fg"` - Cursor string `defaults:"" type:"fg"` + Background string `default:"" type:"bg"` + Foreground string `default:"" type:"fg"` + Outline string `default:"" type:"fg"` + Cursor string `default:"" type:"fg"` } type InputBoxComponent struct { @@ -82,5 +82,5 @@ func (d *InputBoxComponent) SetStyle(c interface{}) { } func InputBox(max_size uint) *InputBoxComponent { - return osui.NewComponent(&InputBoxComponent{max_size: max_size, Style: osui.SetDefaults(&InputBoxStyle{}).(*InputBoxStyle)}).(*InputBoxComponent) + return &InputBoxComponent{max_size: max_size, Style: osui.SetDefaults(&InputBoxStyle{}).(*InputBoxStyle)} } diff --git a/ui/menu.go b/ui/menu.go new file mode 100644 index 0000000..f36a15d --- /dev/null +++ b/ui/menu.go @@ -0,0 +1,91 @@ +package ui + +import ( + "strings" + + "github.com/orus-dev/osui" + "github.com/orus-dev/osui/colors" + "github.com/orus-dev/osui/isKey" +) + +type MenuStyle struct { + Fg string `default:"" type:"fg"` + Bg string `default:"" type:"bg"` + SelectedFg string `default:"\x1b[34m" type:"fg"` + SelectedBg string `default:"" type:"bg"` + Cursor string `default:"> " type:"fg"` + CursorInactive string `default:" " type:"fg"` +} + +type MenuComponent struct { + Data osui.ComponentData + Style *MenuStyle + Items []string + SelectedItem int + onSelected func(*MenuComponent, bool) +} + +func (m *MenuComponent) GetComponentData() *osui.ComponentData { + return &m.Data +} + +func (m *MenuComponent) Render() string { + osui.UseStyle(m.Style) + res := []string{} + + cursor := m.Style.Cursor + if !m.Data.IsActive { + cursor = m.Style.CursorInactive + } + d, _ := osui.CompressString(m.Style.Cursor, "") + empty := strings.Repeat(" ", len(d)) + + for i, item := range m.Items { + if i == m.SelectedItem { + res = append(res, cursor+colors.Combine(m.Style.SelectedFg, m.Style.SelectedBg)+item+colors.Reset+m.Data.DefaultColor) + } else { + res = append(res, empty+colors.Combine(m.Style.Fg, m.Style.Bg)+item+colors.Reset+m.Data.DefaultColor) + } + } + + return strings.Join(res, "\n") +} + +func (m *MenuComponent) Update(key string) bool { + if isKey.Down(key) { + if m.SelectedItem+1 < len(m.Items) { + m.SelectedItem++ + } else { + m.SelectedItem = 0 + } + } else if isKey.Up(key) { + if m.SelectedItem > 0 { + m.SelectedItem-- + } else { + m.SelectedItem = len(m.Items) - 1 + } + } else if isKey.Enter(key) { + m.onSelected(m, true) + return true + } else if isKey.Escape(key) { + m.onSelected(m, false) + return true + } + return false +} + +func (m *MenuComponent) SetStyle(c interface{}) { + m.Style = osui.SetDefaults(c.(*MenuStyle)).(*MenuStyle) +} + +func (m *MenuComponent) OnSelected(f func(*MenuComponent, bool)) *MenuComponent { + m.onSelected = f + return m +} + +func Menu(items ...string) *MenuComponent { + return &MenuComponent{ + Items: items, + Style: osui.SetDefaults(&MenuStyle{}).(*MenuStyle), + } +} diff --git a/ui/paginator.go b/ui/paginator.go index b40eba3..2221ba0 100644 --- a/ui/paginator.go +++ b/ui/paginator.go @@ -97,8 +97,8 @@ func (p *PaginatorComponent) SetStyle(c interface{}) { } func Paginator(pages ...osui.Component) *PaginatorComponent { - return osui.NewComponent(&PaginatorComponent{ + return &PaginatorComponent{ Components: pages, Style: osui.SetDefaults(&PaginatorStyle{}).(*PaginatorStyle), - }).(*PaginatorComponent) + } } diff --git a/ui/text.go b/ui/text.go index aa4c41e..b90ae14 100644 --- a/ui/text.go +++ b/ui/text.go @@ -24,5 +24,5 @@ func (t TextComponent) Render() string { func (t *TextComponent) SetStyle(interface{}) {} func Text(text string) *TextComponent { - return osui.NewComponent(&TextComponent{Text: text}).(*TextComponent) + return &TextComponent{Text: text} } diff --git a/ui/utils.go b/ui/utils.go index a53494b..f384641 100644 --- a/ui/utils.go +++ b/ui/utils.go @@ -18,6 +18,14 @@ func centerText(text string, width int) string { return fmt.Sprintf("%s%s%s", leftPadding, text, rightPadding) } +func fnLogicValue(f func(*ButtonComponent) bool) func(*ButtonComponent) bool { + if f == nil { + return func(*ButtonComponent) bool { return false } + } else { + return f + } +} + func WithPosition(x, y int, c osui.Component) osui.Component { data := c.GetComponentData() data.X = x @@ -36,11 +44,3 @@ func WithSize(width, height int, c osui.Component) osui.Component { data.Height = height return c } - -func fnLogicValue(f func(*ButtonComponent) bool) func(*ButtonComponent) bool { - if f == nil { - return func(*ButtonComponent) bool { return false } - } else { - return f - } -} diff --git a/utils.go b/utils.go index 076a803..00d7804 100644 --- a/utils.go +++ b/utils.go @@ -155,6 +155,7 @@ func SetDefaults(p interface{}) interface{} { } func UseStyle(p interface{}) { + SetDefaults(p) v := reflect.ValueOf(p) if v.Kind() != reflect.Ptr { panic("SetStyle: expected a pointer to a struct")