2 Commits
Author SHA1 Message Date
selimaj-dev 8f375efe41 divs and paginators 2024-09-25 12:29:03 +02:00
selimaj-dev 905a77f8b0 paginator and styles 2024-09-24 20:42:44 +02:00
10 changed files with 451 additions and 103 deletions
+16
View File
@@ -0,0 +1,16 @@
package main
import (
"github.com/orus-dev/osui"
"github.com/orus-dev/osui/styles"
"github.com/orus-dev/osui/ui"
)
func main() {
colors := ui.Text(
styles.Red + "Red!\n" + styles.Green + "Green!\n" + styles.Blue + "Blue!" + styles.Reset,
)
paginator := ui.Paginator(ui.Div(colors), ui.Div(ui.InputBox(30)))
screen := osui.NewScreen(paginator)
screen.Run()
}
+28 -18
View File
@@ -10,28 +10,25 @@ import (
type ComponentData struct { type ComponentData struct {
X uint16 X uint16
Y uint16 Y uint16
Width int
Height int
IsActive bool
Screen *Screen Screen *Screen
} }
type Component interface { type Component interface {
Render() string Render() string
GetComponentData() *ComponentData GetComponentData() *ComponentData
Update(string) bool
} }
type Screen struct { type Screen struct {
components []Component component Component
} }
func NewScreen(c ...Component) *Screen { func NewScreen(c Component) *Screen {
s := &Screen{components: c} HideCursor()
for _, component := range s.components { s := &Screen{component: c}
componentData := component.GetComponentData()
if componentData.Screen == nil {
componentData.Screen = s
} else {
componentData.Screen.components = s.components
}
}
return s return s
} }
@@ -41,13 +38,26 @@ func (s *Screen) Render() error {
if err != nil { if err != nil {
return fmt.Errorf("error getting the terminal size: %s", err) return fmt.Errorf("error getting the terminal size: %s", err)
} }
frame := make([]string, height) frame := NewFrame(width, height)
for i := 0; i < height; i++ { data := s.component.GetComponentData()
frame[i] = strings.Repeat(" ", width) data.Width = width
} data.Height = height
for _, component := range s.components { data.Screen = s
RenderOnFrame(component, &frame) data.IsActive = true
} RenderOnFrame(s.component, &frame)
fmt.Print(strings.Join(frame, "\n")) fmt.Print(strings.Join(frame, "\n"))
return nil return nil
} }
func (s *Screen) Run() {
s.component.GetComponentData().Screen = s
s.component.Update("OSUI:RENDER")
for {
s.Render()
k, _ := ReadKey()
if s.component.Update(k) {
ShowCursor()
return
}
}
}
+36
View File
@@ -0,0 +1,36 @@
package styles
import "fmt"
const (
Reset = "\033[0m"
Bold = "\033[1m"
Underline = "\033[4m"
Italic = "\033[3m"
Reverse = "\033[7m"
Strike = "\033[9m"
// Font Colors
Black = "\033[30m"
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
Blue = "\033[34m"
Magenta = "\033[35m"
Cyan = "\033[36m"
White = "\033[37m"
// Background Colors
BgBlack = "\033[40m"
BgRed = "\033[41m"
BgGreen = "\033[42m"
BgYellow = "\033[43m"
BgBlue = "\033[44m"
BgMagenta = "\033[45m"
BgCyan = "\033[46m"
BgWhite = "\033[47m"
)
func Rgb(r, g, b uint8) string {
return fmt.Sprintf("\x1b[38;2;%d;%d;%dm", r, g, b)
}
-70
View File
@@ -1,70 +0,0 @@
package ui
import (
"fmt"
"strings"
"github.com/orus-dev/osui"
)
type TextComponent struct {
Data osui.ComponentData
Text string
}
func (t *TextComponent) GetComponentData() *osui.ComponentData {
return &t.Data
}
func (t TextComponent) Render() string {
return t.Text
}
type InputBoxComponent struct {
Data osui.ComponentData
max_size uint
InputData string
}
func (s *InputBoxComponent) GetComponentData() *osui.ComponentData {
return &s.Data
}
func (s InputBoxComponent) Render() string {
return fmt.Sprintf(
" %s\n|%s%s|\n %s",
strings.Repeat("_", int(s.max_size)),
s.InputData,
strings.Repeat(" ", int(s.max_size)-len(s.InputData)),
strings.Repeat("‾", int(s.max_size)),
)
}
func (s *InputBoxComponent) Read() error {
for {
fmt.Printf("\x1B[%d;%dH", s.Data.Y+2, int(s.Data.X+2)+len(s.InputData))
key, err := osui.ReadKey()
if err != nil {
fmt.Print("\n\n")
fmt.Println(err)
break
}
switch key {
case osui.Key.Enter:
fmt.Print("\n\n")
return nil
case osui.Key.Backspace:
if len(s.InputData) > 0 {
s.InputData = s.InputData[:len(s.InputData)-1]
}
default:
if int(s.max_size) > len(s.InputData) {
s.InputData += key
}
}
s.Data.Screen.Render()
}
return nil
}
+81
View File
@@ -0,0 +1,81 @@
package ui
import (
"strings"
"github.com/orus-dev/osui"
)
type DivComponent struct {
Data osui.ComponentData
Components []osui.Component
ActiveComponent int
}
func (p *DivComponent) GetComponentData() *osui.ComponentData {
return &p.Data
}
func (p DivComponent) Render() string {
frame := osui.NewFrame(p.Data.Width, p.Data.Height)
for _, c := range p.Components {
data := c.GetComponentData()
data.Width = p.Data.Width
data.Height = p.Data.Height
data.Screen = p.Data.Screen
osui.RenderOnFrame(c, &frame)
}
return strings.Join(frame, "\n")
}
func (p *DivComponent) Run() {
for _, c := range p.Components {
d := c.GetComponentData()
d.Screen = p.Data.Screen
}
for {
key, _ := osui.ReadKey()
if p.Update(key) {
return
}
}
}
func (p *DivComponent) Update(key string) bool {
switch key {
case "":
p.updateActive(p.ActiveComponent)
case osui.Key.Up:
p.updateActive(p.ActiveComponent - 1)
case osui.Key.Down:
p.updateActive(p.ActiveComponent + 1)
default:
if len(p.Components) > 0 {
p.Components[p.ActiveComponent].GetComponentData().IsActive = p.Data.IsActive
if p.Components[p.ActiveComponent].Update(key) {
if p.ActiveComponent < len(p.Components)-1 {
p.updateActive(p.ActiveComponent + 1)
} else {
return true
}
}
}
}
return false
}
func (p *DivComponent) updateActive(newIndex int) {
if newIndex >= 0 && newIndex < len(p.Components) && len(p.Components) > 0 {
p.Components[p.ActiveComponent].GetComponentData().IsActive = false
p.ActiveComponent = newIndex
p.Components[p.ActiveComponent].GetComponentData().IsActive = p.Data.IsActive
p.Components[p.ActiveComponent].Update("")
}
}
func Div(components ...osui.Component) *DivComponent {
return &DivComponent{
Components: components,
}
}
+76
View File
@@ -0,0 +1,76 @@
package ui
import (
"fmt"
"strings"
"github.com/orus-dev/osui"
)
type InputBoxComponent struct {
Data osui.ComponentData
max_size uint
InputData string
}
func (s *InputBoxComponent) GetComponentData() *osui.ComponentData {
return &s.Data
}
func (s InputBoxComponent) Render() string {
if s.max_size > uint(len(s.InputData)) {
return fmt.Sprintf(
" %s\n|%s%s|\n %s",
strings.Repeat("_", int(s.max_size)),
s.InputData+osui.LogicValue(s.Data.IsActive, "█", ""),
strings.Repeat(" ", int(s.max_size)-len(s.InputData)-osui.LogicValueInt(s.Data.IsActive, 1, 0)),
strings.Repeat("‾", int(s.max_size)),
)
}
return fmt.Sprintf(
" %s\n|%s\n %s",
strings.Repeat("_", int(s.max_size)),
s.InputData+osui.LogicValue(s.Data.IsActive, "█", "|"),
strings.Repeat("‾", int(s.max_size)),
)
}
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 {
switch key {
case "":
case osui.Key.Enter:
return true
case osui.Key.Backspace:
if len(s.InputData) > 0 {
s.InputData = s.InputData[:len(s.InputData)-1]
}
default:
if len(key) == 1 {
if int(s.max_size) > len(s.InputData) {
s.InputData += key
}
}
}
return false
}
func InputBox(max_size uint) *InputBoxComponent {
return &InputBoxComponent{max_size: max_size}
}
+94
View File
@@ -0,0 +1,94 @@
package ui
import (
"fmt"
"strings"
"github.com/orus-dev/osui"
"github.com/orus-dev/osui/styles"
)
type PaginatorStyle struct {
Active string `default:"\033[32m"`
}
type PaginatorComponent struct {
Data osui.ComponentData
Style *PaginatorStyle
Components []osui.Component
ActiveComponent int
}
func (p *PaginatorComponent) GetComponentData() *osui.ComponentData {
return &p.Data
}
func (p PaginatorComponent) Render() string {
pgs := strings.Repeat(" ", p.Data.Width/2)
for page, c := range p.Components {
if page == p.ActiveComponent {
pgs += p.Style.Active + "•" + styles.Reset
} else {
pgs += styles.Reset + "•"
}
d := c.GetComponentData()
if d.Screen == nil {
d.Screen = p.Data.Screen
}
d.Height = p.Data.Height - 1
d.Width = p.Data.Width
}
frame := osui.NewFrame(p.Data.Width, p.Data.Height-3)
osui.RenderOnFrame(p.Components[p.ActiveComponent], &frame)
return fmt.Sprintf("%s\n%s", pgs, styles.Reset+strings.Join(frame, "\n"))
}
func (p *PaginatorComponent) Update(key string) bool {
switch key {
case "":
p.updateActive(p.ActiveComponent)
case "\x1b[Z":
if p.ActiveComponent > 0 {
p.updateActive(p.ActiveComponent - 1)
} else {
p.updateActive(len(p.Components) - 1)
}
case osui.Key.Tab:
if p.ActiveComponent < len(p.Components)-1 {
p.updateActive(p.ActiveComponent + 1)
} else {
p.updateActive(0)
}
case osui.Key.Escape:
fmt.Print("\n\n")
return true
default:
if len(p.Components) > 0 {
p.Components[p.ActiveComponent].GetComponentData().IsActive = p.Data.IsActive
if p.Components[p.ActiveComponent].Update(key) {
if p.ActiveComponent < len(p.Components)-1 {
p.updateActive(p.ActiveComponent + 1)
} else {
p.updateActive(0)
}
}
}
}
return false
}
func (p *PaginatorComponent) updateActive(newIndex int) {
if newIndex >= 0 && newIndex < len(p.Components) && len(p.Components) > 0 {
p.Components[p.ActiveComponent].GetComponentData().IsActive = false
p.ActiveComponent = newIndex
p.Components[p.ActiveComponent].GetComponentData().IsActive = p.Data.IsActive
p.Components[p.ActiveComponent].Update("")
}
}
func Paginator(pages ...osui.Component) *PaginatorComponent {
return &PaginatorComponent{
Components: pages,
Style: osui.SetDefaults(&PaginatorStyle{}).(*PaginatorStyle),
}
}
+26
View File
@@ -0,0 +1,26 @@
package ui
import (
"github.com/orus-dev/osui"
)
type TextComponent struct {
Data osui.ComponentData
Text string
}
func (t *TextComponent) Update(string) bool {
return false
}
func (t *TextComponent) GetComponentData() *osui.ComponentData {
return &t.Data
}
func (t TextComponent) Render() string {
return t.Text
}
func Text(text string) *TextComponent {
return &TextComponent{Text: text}
}
-9
View File
@@ -1,9 +0,0 @@
package ui
func Text(text string) *TextComponent {
return &TextComponent{Text: text}
}
func InputBox(max_size uint) *InputBoxComponent {
return &InputBoxComponent{max_size: max_size}
}
+91 -3
View File
@@ -3,6 +3,10 @@ package osui
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"reflect"
"regexp"
"runtime"
"strings" "strings"
"golang.org/x/term" "golang.org/x/term"
@@ -27,13 +31,36 @@ func renderLine(frameLine, line string, x int) string {
func RenderOnFrame(c Component, frame *[]string) { func RenderOnFrame(c Component, frame *[]string) {
componentData := c.GetComponentData() componentData := c.GetComponentData()
for i, line := range strings.Split(c.Render(), "\n") { for i, lo := range strings.Split(c.Render(), "\n") {
if int(componentData.Y)+i < len(*frame) { if int(componentData.Y)+i < len(*frame) {
(*frame)[int(componentData.Y)+i] = renderLine((*frame)[int(componentData.Y)+i], line, int(componentData.X)) fo := (*frame)[int(componentData.Y)+i]
f, fa := CompressString(fo, "\b")
line, la := CompressString(lo, "\t")
r := renderLine(f, line, int(componentData.X))
(*frame)[int(componentData.Y)+i] = DecompressString(DecompressString(r, la, "\t"), fa, "\b")
} }
} }
} }
func CompressString(input, repl string) (string, []string) {
pattern := `\x1b\[([0-9;]*)m`
re := regexp.MustCompile(pattern)
matches := re.FindAllString(input, -1)
return re.ReplaceAllString(input, repl), matches
}
func DecompressString(modified string, outputArray []string, c string) string {
parts := strings.Split(modified, c)
reconstructed := ""
for i, part := range parts {
reconstructed += part
if i < len(outputArray) {
reconstructed += outputArray[i]
}
}
return reconstructed
}
func ReadKey() (string, error) { func ReadKey() (string, error) {
oldState, err := term.MakeRaw(int(os.Stdin.Fd())) oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil { if err != nil {
@@ -48,8 +75,69 @@ func ReadKey() (string, error) {
return string(b[:n]), nil return string(b[:n]), nil
} }
func NewFrame(width, height int) []string {
frame := make([]string, height)
for i := 0; i < height; i++ {
frame[i] = strings.Repeat(" ", width)
}
return frame
}
func Clear() { func Clear() {
fmt.Print("\x1b[H\x1b[2J\x1b[3J") switch runtime.GOOS {
case "windows":
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
default:
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
}
func SetDefaults(p interface{}) interface{} {
v := reflect.ValueOf(p)
if v.Kind() != reflect.Ptr {
panic("setDefaults: expected a pointer to a struct")
}
val := v.Elem()
if val.Kind() != reflect.Struct {
panic("setDefaults: expected a pointer to a struct")
}
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
structField := typ.Field(i)
if defaultValue, ok := structField.Tag.Lookup("default"); ok {
if field.Kind() == reflect.String && field.String() == "" {
field.SetString(defaultValue)
}
}
}
return p
}
func ShowCursor() {
fmt.Print("\033[?25h")
}
func HideCursor() {
fmt.Print("\033[?25l")
}
func LogicValue(b bool, _if, _else string) string {
if b {
return _if
}
return _else
}
func LogicValueInt(b bool, _if, _else int) int {
if b {
return _if
}
return _else
} }
type Key_ = string type Key_ = string