First version
This commit is contained in:
+13
-18
@@ -13,48 +13,43 @@ func main() {
|
|||||||
|
|
||||||
func App() *PaginatorComponent {
|
func App() *PaginatorComponent {
|
||||||
w, h := osui.GetTerminalSize()
|
w, h := osui.GetTerminalSize()
|
||||||
text := Text("")
|
|
||||||
return Paginator(
|
return Paginator(
|
||||||
// Page 1
|
// Page 1
|
||||||
Div(
|
Div(
|
||||||
Text("Welcome to the example! Press tab to go to the next page or press shift + tab to go to a previous page"),
|
Text("Welcome to the example! The blue text on the bottom show the keybindings"),
|
||||||
|
WithPosition(0, h-2, Text(colors.Blue+"Next Page: Tab. Previous Page: shift + tab")),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Page 2
|
// Page 2
|
||||||
Div(
|
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(0, 0, Text("This blue square is a div. A div stores multiple components into one")),
|
||||||
WithPosition((w-30)/2, (h-4)/2,
|
WithPosition((w-30)/2, (h-4)/2,
|
||||||
WithSize(30, 4,
|
WithSize(30, 5,
|
||||||
WithStyle(&DivStyle{Background: colors.Blue, Foreground: colors.Red},
|
WithStyle(&DivStyle{Outline: colors.Blue},
|
||||||
Div(WithPosition(0, 0, Text("Hello, World!"))),
|
Div(WithPosition(4, 1, Button("Hello, World!", BtnParams{Width: 19}))),
|
||||||
))),
|
))),
|
||||||
|
WithPosition(0, h-2, Text(colors.Blue+"Navigate: Ctrl+(W/A/S/D)")),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Page 3
|
// Page 3
|
||||||
Div(
|
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(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)),
|
WithPosition((w-32)/2, (h-3)/2, InputBox(30)),
|
||||||
|
WithPosition(0, h-2, Text(colors.Blue+"Navigate: W/S. Select: Enter. Exit Menu: Q. ")),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Page 4
|
// Page 4
|
||||||
Div(
|
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(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 }})),
|
WithPosition((w-20)/2, (h-3)/2, Button("This is a button", BtnParams{OnClick: func(bc *ButtonComponent) bool { return false }})),
|
||||||
|
WithPosition(0, h-2, Text(colors.Blue+"Navigate: Ctrl+(W/A/S/D). Click Button: Enter")),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Page 5
|
// Page 5
|
||||||
Div(
|
Div(
|
||||||
WithPosition(0, 2, Menu("Item 1", "Item 2", "Item 3").OnSelected(func(m *MenuComponent, b bool) {
|
WithPosition(0, 0, Text("This is a Menu. It prompts you to select a item. "+colors.Red+colors.Bold+"NOTE: This Menu is inside a div")),
|
||||||
if b {
|
WithPosition(0, 2, Menu("Item 1", "Item 2", "Item 3", "Item 4", "Item 5").OnSelected(func(m *MenuComponent, b bool) {})),
|
||||||
text.Text = m.Items[m.SelectedItem]
|
WithPosition(0, h-2, Text(colors.Blue+"Navigate: Ctrl+(W/A/S/D). Navigate Menu: W/S. Select: Enter. Exit Menu: Q. ")),
|
||||||
}
|
),
|
||||||
})),
|
|
||||||
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 "" }),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,3 +31,23 @@ func Right(s string) bool {
|
|||||||
func Left(s string) bool {
|
func Left(s string) bool {
|
||||||
return s == "\x1b[D"
|
return s == "\x1b[D"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Char(s string, s1 string) bool {
|
||||||
|
return s == s1
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlW(s string) bool {
|
||||||
|
return s == "\x17"
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlS(s string) bool {
|
||||||
|
return s == "\x13"
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlA(s string) bool {
|
||||||
|
return s == "\x01"
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlD(s string) bool {
|
||||||
|
return s == "\x04"
|
||||||
|
}
|
||||||
@@ -25,8 +25,7 @@ type Component interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Screen struct {
|
type Screen struct {
|
||||||
component Component
|
component Component
|
||||||
CustomRender func()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScreen(c Component) *Screen {
|
func NewScreen(c Component) *Screen {
|
||||||
@@ -35,11 +34,7 @@ func NewScreen(c Component) *Screen {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screen) Render() error {
|
func (s *Screen) Render() {
|
||||||
if s.CustomRender != nil {
|
|
||||||
s.CustomRender()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Clear()
|
Clear()
|
||||||
width, height := GetTerminalSize()
|
width, height := GetTerminalSize()
|
||||||
frame := NewFrame(width, height)
|
frame := NewFrame(width, height)
|
||||||
@@ -55,7 +50,6 @@ func (s *Screen) Render() error {
|
|||||||
data.DefaultColor = colors.Reset
|
data.DefaultColor = colors.Reset
|
||||||
RenderOnFrame(s.component, &frame)
|
RenderOnFrame(s.component, &frame)
|
||||||
fmt.Print(strings.Join(frame, "\n"))
|
fmt.Print(strings.Join(frame, "\n"))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screen) Run() {
|
func (s *Screen) Run() {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
type DivStyle struct {
|
type DivStyle struct {
|
||||||
Background string `default:"" type:"bg"`
|
Background string `default:"" type:"bg"`
|
||||||
Foreground string `default:"" type:"fg"`
|
Foreground string `default:"" type:"fg"`
|
||||||
|
Outline string `default:"" type:"fg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DivComponent struct {
|
type DivComponent struct {
|
||||||
@@ -18,7 +19,6 @@ type DivComponent struct {
|
|||||||
Style *DivStyle
|
Style *DivStyle
|
||||||
Components []osui.Component
|
Components []osui.Component
|
||||||
ActiveComponent int
|
ActiveComponent int
|
||||||
onKey func(*DivComponent, string) string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DivComponent) GetComponentData() *osui.ComponentData {
|
func (d *DivComponent) GetComponentData() *osui.ComponentData {
|
||||||
@@ -31,7 +31,7 @@ func (d *DivComponent) Render() string {
|
|||||||
for i, c := range d.Components {
|
for i, c := range d.Components {
|
||||||
data := c.GetComponentData()
|
data := c.GetComponentData()
|
||||||
if i == d.ActiveComponent {
|
if i == d.ActiveComponent {
|
||||||
data.IsActive = true
|
data.IsActive = d.Data.IsActive
|
||||||
} else {
|
} else {
|
||||||
data.IsActive = false
|
data.IsActive = false
|
||||||
}
|
}
|
||||||
@@ -45,41 +45,28 @@ func (d *DivComponent) Render() string {
|
|||||||
data.Screen = d.Data.Screen
|
data.Screen = d.Data.Screen
|
||||||
osui.RenderOnFrame(c, &frame)
|
osui.RenderOnFrame(c, &frame)
|
||||||
}
|
}
|
||||||
for i, f := range frame {
|
if d.Style.Outline == "" {
|
||||||
frame[i] = colors.Combine(d.Style.Foreground, d.Style.Background) + f + colors.Reset
|
for i, f := range frame {
|
||||||
|
frame[i] = colors.Combine(d.Style.Foreground, d.Style.Background) + f + colors.Reset
|
||||||
|
}
|
||||||
|
return strings.Join(frame, "\n")
|
||||||
|
} else {
|
||||||
|
for i, f := range frame {
|
||||||
|
frame[i] = d.Style.Outline + "│" + colors.Combine(d.Style.Foreground, d.Style.Background) + f + colors.Reset + d.Style.Outline + "│" + colors.Reset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(frame, "\n")
|
return " " + d.Style.Outline + strings.Repeat("_", d.Data.Width) + colors.Reset + "\n" + strings.Join(frame, "\n") + "\n " + d.Style.Outline + strings.Repeat("‾", d.Data.Width) + colors.Reset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DivComponent) Update(key string) bool {
|
func (d *DivComponent) Update(key string) bool {
|
||||||
if d.onKey != nil {
|
if isKey.CtrlW(key) {
|
||||||
o := d.onKey(d, key)
|
d.updateActive(findClosestComponent(d.Components, d.ActiveComponent, "up"))
|
||||||
if o == "up" {
|
} else if isKey.CtrlS(key) {
|
||||||
d.updateActive(d.ActiveComponent - 1)
|
d.updateActive(findClosestComponent(d.Components, d.ActiveComponent, "down"))
|
||||||
} else if o == "down" {
|
} else if isKey.CtrlA(key) {
|
||||||
d.updateActive(d.ActiveComponent + 1)
|
d.updateActive(findClosestComponent(d.Components, d.ActiveComponent, "left"))
|
||||||
} else if o == "true" {
|
} else if isKey.CtrlD(key) {
|
||||||
return true
|
d.updateActive(findClosestComponent(d.Components, d.ActiveComponent, "right"))
|
||||||
} 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) {
|
|
||||||
d.updateActive(d.ActiveComponent + 1)
|
|
||||||
} else {
|
} else {
|
||||||
if len(d.Components) > 0 {
|
if len(d.Components) > 0 {
|
||||||
d.Components[d.ActiveComponent].GetComponentData().IsActive = d.Data.IsActive
|
d.Components[d.ActiveComponent].GetComponentData().IsActive = d.Data.IsActive
|
||||||
@@ -105,11 +92,6 @@ 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 {
|
func (d *DivComponent) AddTo(c *DivComponent) *DivComponent {
|
||||||
*c = *d
|
*c = *d
|
||||||
return d
|
return d
|
||||||
|
|||||||
+3
-3
@@ -52,13 +52,13 @@ func (m *MenuComponent) Render() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MenuComponent) Update(key string) bool {
|
func (m *MenuComponent) Update(key string) bool {
|
||||||
if isKey.Down(key) {
|
if isKey.Char(key, "s") {
|
||||||
if m.SelectedItem+1 < len(m.Items) {
|
if m.SelectedItem+1 < len(m.Items) {
|
||||||
m.SelectedItem++
|
m.SelectedItem++
|
||||||
} else {
|
} else {
|
||||||
m.SelectedItem = 0
|
m.SelectedItem = 0
|
||||||
}
|
}
|
||||||
} else if isKey.Up(key) {
|
} else if isKey.Char(key, "w") {
|
||||||
if m.SelectedItem > 0 {
|
if m.SelectedItem > 0 {
|
||||||
m.SelectedItem--
|
m.SelectedItem--
|
||||||
} else {
|
} else {
|
||||||
@@ -67,7 +67,7 @@ func (m *MenuComponent) Update(key string) bool {
|
|||||||
} else if isKey.Enter(key) {
|
} else if isKey.Enter(key) {
|
||||||
m.onSelected(m, true)
|
m.onSelected(m, true)
|
||||||
return true
|
return true
|
||||||
} else if isKey.Escape(key) {
|
} else if isKey.Char(key, "q") {
|
||||||
m.onSelected(m, false)
|
m.onSelected(m, false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+50
@@ -2,6 +2,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/orus-dev/osui"
|
"github.com/orus-dev/osui"
|
||||||
@@ -44,3 +45,52 @@ func WithSize(width, height int, c osui.Component) osui.Component {
|
|||||||
data.Height = height
|
data.Height = height
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findClosestComponent(a []osui.Component, i int, d string) int {
|
||||||
|
if len(a) == 0 || i < 0 || i >= len(a) {
|
||||||
|
return -1 // Invalid input
|
||||||
|
}
|
||||||
|
|
||||||
|
current := a[i].GetComponentData()
|
||||||
|
closestIndex := -1
|
||||||
|
minDistance := math.MaxFloat64
|
||||||
|
|
||||||
|
for j, c := range a {
|
||||||
|
component := c.GetComponentData()
|
||||||
|
if i == j {
|
||||||
|
continue // Skip the current component
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the component is in the correct direction
|
||||||
|
isValidDirection := false
|
||||||
|
switch d {
|
||||||
|
case "up":
|
||||||
|
if component.Y < current.Y {
|
||||||
|
isValidDirection = true
|
||||||
|
}
|
||||||
|
case "down":
|
||||||
|
if component.Y > current.Y {
|
||||||
|
isValidDirection = true
|
||||||
|
}
|
||||||
|
case "left":
|
||||||
|
if component.X < current.X {
|
||||||
|
isValidDirection = true
|
||||||
|
}
|
||||||
|
case "right":
|
||||||
|
if component.X > current.X {
|
||||||
|
isValidDirection = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the direction is valid, calculate distance
|
||||||
|
if isValidDirection {
|
||||||
|
distance := math.Hypot(float64(component.X-current.X), float64(component.Y-current.Y))
|
||||||
|
if distance < minDistance {
|
||||||
|
minDistance = distance
|
||||||
|
closestIndex = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestIndex
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,48 +13,18 @@ import (
|
|||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: for now keep this code in case of needing it later
|
|
||||||
|
|
||||||
// func renderLine(frameLine, line string, x int) string {
|
|
||||||
// result := []rune(frameLine)
|
|
||||||
// lineRunes := []rune(line)
|
|
||||||
// x += strings.Count(string(result[:(x+1)]), "\t") + strings.Count(string(result[:(x+1)]), "\b")
|
|
||||||
|
|
||||||
// for range strings.Count(line, "\t") + strings.Count(line, "\b") {
|
|
||||||
// result = append(result, ' ')
|
|
||||||
// }
|
|
||||||
|
|
||||||
// frameLen := len(result)
|
|
||||||
|
|
||||||
// for i := 0; i < len(lineRunes) && x+i < frameLen; i++ {
|
|
||||||
// result[x+i] = lineRunes[i]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return string(result)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// lineRunes := []rune(line)
|
|
||||||
// x += strings.Count(string(frameLine[:(x+1)]), "\b")
|
|
||||||
// result = append(result, []rune(strings.Repeat(" ", strings.Count(line, "\t")))...)
|
|
||||||
// frameLen := len(result)
|
|
||||||
// d := strings.Count(frameLine, "\b") - strings.Count(string(result), "\b")
|
|
||||||
// for i := 0; i < len(lineRunes) && x+i < frameLen; i++ {
|
|
||||||
// result[x+i] = lineRunes[i]
|
|
||||||
// }
|
|
||||||
// NOTE: for now keep this code in case of needing it later
|
|
||||||
|
|
||||||
func renderLine(frameLine, line_ string, x int) string {
|
func renderLine(frameLine, line_ string, x int) string {
|
||||||
result := []rune(frameLine)
|
frame := []rune(frameLine)
|
||||||
line := []rune(line_)
|
line := []rune(line_)
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
v := 0
|
v := 0
|
||||||
for j, c := range result {
|
for j, c := range frame {
|
||||||
if i+j >= x && v < len(line) {
|
if i+j >= x && v < len(line) {
|
||||||
if c != '\b' && line[v] == '\t' {
|
if c != '\b' && line[v] == '\t' {
|
||||||
result = append(result, ' ')
|
frame = append(frame[:i+j], append([]rune{' '}, frame[i+j:]...)...)
|
||||||
}
|
}
|
||||||
result[i+j] = line[v]
|
frame[i+j] = line[v]
|
||||||
v++
|
v++
|
||||||
}
|
}
|
||||||
if c == '\b' {
|
if c == '\b' {
|
||||||
@@ -62,7 +32,7 @@ func renderLine(frameLine, line_ string, x int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(result)
|
return string(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RenderOnFrame(c Component, frame *[]string) {
|
func RenderOnFrame(c Component, frame *[]string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user