From 90f106f127681342667b5082221f1f67ad52d4b8 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 28 Sep 2024 17:12:45 +0200 Subject: [PATCH] buttons and styles --- cutils/cutils.go | 22 --------- example/main.go | 54 +++++++++++++-------- isKey/key.go | 2 +- osui.go | 12 ++++- ui/button.go | 121 +++++++++++++++++++++++++++++++++++++++++++++++ ui/div.go | 17 +------ ui/inputBox.go | 18 +------ ui/paginator.go | 4 +- ui/text.go | 2 +- ui/utils.go | 46 ++++++++++++++++++ 10 files changed, 218 insertions(+), 80 deletions(-) delete mode 100644 cutils/cutils.go create mode 100644 ui/button.go create mode 100644 ui/utils.go diff --git a/cutils/cutils.go b/cutils/cutils.go deleted file mode 100644 index 84a650e..0000000 --- a/cutils/cutils.go +++ /dev/null @@ -1,22 +0,0 @@ -package cutils - -import "github.com/orus-dev/osui" - -func WithPosition(x, y int, c osui.Component) osui.Component { - data := c.GetComponentData() - data.X = x - data.Y = y - return c -} - -func WithStyle(style interface{}, c osui.Component) osui.Component { - c.SetStyle(style) - return c -} - -func WithSize(width, height int, c osui.Component) osui.Component { - data := c.GetComponentData() - data.Width = width - data.Height = height - return c -} diff --git a/example/main.go b/example/main.go index ac7050a..b943ca8 100644 --- a/example/main.go +++ b/example/main.go @@ -3,28 +3,42 @@ package main import ( "github.com/orus-dev/osui" "github.com/orus-dev/osui/colors" - "github.com/orus-dev/osui/cutils" - "github.com/orus-dev/osui/ui" + . "github.com/orus-dev/osui/ui" ) func main() { - w, h := osui.GetTerminalSize() - paginator := ui.Paginator( - ui.Div( - ui.Text("Welcome to the example! Press tab to go to the next page or press shift + tab to go to a previous page"), - ), - ui.Div( - cutils.WithPosition(0, 0, ui.Text("This blue square is a div. A div stores multiple components into one. To navigate to each of those components you can use arrow keys ↑↓")), - cutils.WithPosition((w-30)/2, (h-4)/2, - cutils.WithSize(30, 4, - cutils.WithStyle(&ui.DivStyle{Background: colors.Blue, Foreground: colors.Red}, - ui.Div(cutils.WithPosition(0, 0, ui.Text("Hello, World!"))), - )))), - ui.Div( - cutils.WithPosition(0, 0, ui.Text("This is a InputBox. It takes user input just like a GUI. "+colors.Red+colors.Bold+"NOTE: This InputBox is inside a div")), - cutils.WithPosition((w-32)/2, (h-3)/2, ui.InputBox(30)), - ), - ) - screen := osui.NewScreen(paginator) + screen := osui.NewScreen(App()) screen.Run() } + +func App() *PaginatorComponent { + w, h := osui.GetTerminalSize() + return Paginator( + // Page 1 + Div( + Text("Welcome to the example! Press tab to go to the next page or press shift + tab to go to a previous page"), + ), + + // Page 2 + Div( + WithPosition(0, 0, Text("This blue square is a div. A div stores multiple components into one. To navigate to each of those components you can use arrow keys ↑↓")), + WithPosition((w-30)/2, (h-4)/2, + WithSize(30, 4, + WithStyle(&DivStyle{Background: colors.Blue, Foreground: colors.Red}, + Div(WithPosition(0, 0, Text("Hello, World!"))), + ))), + ), + + // Page 3 + Div( + WithPosition(0, 0, Text("This is a InputBox. It takes user input just like a GUI. "+colors.Red+colors.Bold+"NOTE: This InputBox is inside a div")), + WithPosition((w-32)/2, (h-3)/2, InputBox(30)), + ), + + // Page 4 + Div( + 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 }})), + ), + ) +} diff --git a/isKey/key.go b/isKey/key.go index 7eff812..67e508a 100644 --- a/isKey/key.go +++ b/isKey/key.go @@ -1,7 +1,7 @@ package isKey func Enter(s string) bool { - return s == "\r" + return s == "\r" || s == "\n" } func Tab(s string) bool { diff --git a/osui.go b/osui.go index 16835dd..f777f38 100644 --- a/osui.go +++ b/osui.go @@ -15,6 +15,13 @@ 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 { @@ -59,11 +66,12 @@ func (s *Screen) Render() error { } func (s *Screen) Run() { - s.component.GetComponentData().Screen = s + data := s.component.GetComponentData() + data.Screen = s for { s.Render() k, _ := ReadKey() - if s.component.Update(k) { + if data.Update(k) { ShowCursor() return } diff --git a/ui/button.go b/ui/button.go new file mode 100644 index 0000000..d694b3c --- /dev/null +++ b/ui/button.go @@ -0,0 +1,121 @@ +package ui + +import ( + "fmt" + "strings" + "time" + + "github.com/orus-dev/osui" + "github.com/orus-dev/osui/colors" + "github.com/orus-dev/osui/isKey" +) + +type ButtonStyle struct { + Background string `default:"" type:"bg"` + Foreground string `default:"" type:"fg"` + Outline string `default:"" type:"fg"` + + ActiveBackground string `default:"" type:"bg"` + ActiveForeground string `default:"\x1b[34m" type:"fg"` + ActiveOutline string `default:"" type:"fg"` + + ClickedBackground string `default:"" type:"bg"` + ClickedForeground string `default:"\x1b[32m" type:"fg"` + ClickedOutline string `default:"" type:"fg"` +} + +type ButtonComponent struct { + Data osui.ComponentData + Style *ButtonStyle + Text string + Toggle bool + Clicked bool + OnClick func(*ButtonComponent) bool +} + +func (b *ButtonComponent) Render() string { + osui.UseStyle(b.Style) + + if b.Clicked { + return fmt.Sprintf(" %s\n%s│%s│%s\n %s", + colors.Reset+b.Style.ClickedOutline+strings.Repeat("_", b.Data.Width)+colors.Reset+b.Data.DefaultColor, + colors.Reset+b.Style.ClickedOutline, + colors.Reset+colors.Combine(b.Style.ClickedBackground, b.Style.ClickedForeground)+centerText(b.Text, b.Data.Width)+colors.Reset+b.Style.ClickedOutline, + colors.Reset+b.Data.DefaultColor, + colors.Reset+b.Style.ClickedOutline+strings.Repeat("‾", b.Data.Width)+colors.Reset+b.Data.DefaultColor, + ) + } + + if b.Data.IsActive { + return fmt.Sprintf(" %s\n%s│%s│%s\n %s", + colors.Reset+b.Style.ActiveOutline+strings.Repeat("_", b.Data.Width)+colors.Reset+b.Data.DefaultColor, + colors.Reset+b.Style.ActiveOutline, + colors.Reset+colors.Combine(b.Style.ActiveBackground, b.Style.ActiveForeground)+centerText(b.Text, b.Data.Width)+colors.Reset+b.Style.ActiveOutline, + colors.Reset+b.Data.DefaultColor, + colors.Reset+b.Style.ActiveOutline+strings.Repeat("‾", b.Data.Width)+colors.Reset+b.Data.DefaultColor, + ) + } + + return fmt.Sprintf(" %s\n%s│%s│%s\n %s", + colors.Reset+b.Style.Outline+strings.Repeat("_", b.Data.Width)+colors.Reset+b.Data.DefaultColor, + colors.Reset+b.Style.Outline, + colors.Reset+colors.Combine(b.Style.Background, b.Style.Foreground)+centerText(b.Text, b.Data.Width)+colors.Reset+b.Style.Outline, + colors.Reset+b.Data.DefaultColor, + colors.Reset+b.Style.Outline+strings.Repeat("‾", b.Data.Width)+colors.Reset+b.Data.DefaultColor, + ) +} + +func (b *ButtonComponent) Update(key string) bool { + if isKey.Enter(key) { + if !b.OnClick(b) { + if b.Toggle { + b.Clicked = !b.Clicked + return false + } + b.Clicked = true + b.Data.Screen.Render() + time.Sleep(time.Millisecond * 120) + b.Clicked = false + } + } + return false +} + +func (b *ButtonComponent) GetComponentData() *osui.ComponentData { + return &b.Data +} + +func (b *ButtonComponent) SetStyle(style interface{}) { + b.Style = osui.SetDefaults(style).(*ButtonStyle) +} + +type BtnParams struct { + Style ButtonStyle + Toggle bool + OnClick func(*ButtonComponent) bool + Width int +} + +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), + Data: osui.ComponentData{ + Width: 20, + Height: 1, + }, + OnClick: func(bc *ButtonComponent) bool { return false }, + }).(*ButtonComponent) +} diff --git a/ui/div.go b/ui/div.go index 7e388a6..eee35af 100644 --- a/ui/div.go +++ b/ui/div.go @@ -50,19 +50,6 @@ func (d *DivComponent) Render() string { return strings.Join(frame, "\n") } -func (d *DivComponent) Run() { - for _, c := range d.Components { - data := c.GetComponentData() - data.Screen = d.Data.Screen - } - for { - key, _ := osui.ReadKey() - if d.Update(key) { - return - } - } -} - func (d *DivComponent) Update(key string) bool { if isKey.Up(key) { d.updateActive(d.ActiveComponent - 1) @@ -94,8 +81,8 @@ func (d *DivComponent) updateActive(newIndex int) { } func Div(components ...osui.Component) *DivComponent { - return &DivComponent{ + return osui.NewComponent(&DivComponent{ Components: components, Style: osui.SetDefaults(&DivStyle{}).(*DivStyle), - } + }).(*DivComponent) } diff --git a/ui/inputBox.go b/ui/inputBox.go index 293007d..522f735 100644 --- a/ui/inputBox.go +++ b/ui/inputBox.go @@ -52,22 +52,6 @@ func (s InputBoxComponent) Render() string { ) } -func (s *InputBoxComponent) Read() error { - for { - key, err := osui.ReadKey() - if err != nil { - fmt.Print("\n\n") - fmt.Println(err) - break - } - if s.Update(key) { - fmt.Print("\n\n") - return nil - } - } - return nil -} - func (s *InputBoxComponent) Update(key string) bool { if isKey.Enter(key) { return true @@ -98,5 +82,5 @@ func (d *InputBoxComponent) SetStyle(c interface{}) { } func InputBox(max_size uint) *InputBoxComponent { - return &InputBoxComponent{max_size: max_size, Style: osui.SetDefaults(&InputBoxStyle{}).(*InputBoxStyle)} + return osui.NewComponent(&InputBoxComponent{max_size: max_size, Style: osui.SetDefaults(&InputBoxStyle{}).(*InputBoxStyle)}).(*InputBoxComponent) } diff --git a/ui/paginator.go b/ui/paginator.go index 2221ba0..b40eba3 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 &PaginatorComponent{ + return osui.NewComponent(&PaginatorComponent{ Components: pages, Style: osui.SetDefaults(&PaginatorStyle{}).(*PaginatorStyle), - } + }).(*PaginatorComponent) } diff --git a/ui/text.go b/ui/text.go index b90ae14..aa4c41e 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 &TextComponent{Text: text} + return osui.NewComponent(&TextComponent{Text: text}).(*TextComponent) } diff --git a/ui/utils.go b/ui/utils.go new file mode 100644 index 0000000..a53494b --- /dev/null +++ b/ui/utils.go @@ -0,0 +1,46 @@ +package ui + +import ( + "fmt" + "strings" + + "github.com/orus-dev/osui" +) + +func centerText(text string, width int) string { + if len(text) > width { + return text[:width] + } + padding := (width - len(text)) / 2 + leftPadding := strings.Repeat(" ", padding) + rightPadding := strings.Repeat(" ", padding) + + return fmt.Sprintf("%s%s%s", leftPadding, text, rightPadding) +} + +func WithPosition(x, y int, c osui.Component) osui.Component { + data := c.GetComponentData() + data.X = x + data.Y = y + return c +} + +func WithStyle(style interface{}, c osui.Component) osui.Component { + c.SetStyle(style) + return c +} + +func WithSize(width, height int, c osui.Component) osui.Component { + data := c.GetComponentData() + data.Width = width + 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 + } +}