Stan преди 1 ден
родител
ревизия
f49bba49f8
променени са 24 файла, в които са добавени 325 реда и са изтрити 57 реда
  1. 7 0
      component_button.go
  2. 1 0
      component_code.go
  3. 12 1
      component_container.go
  4. 3 1
      component_image.go
  5. 3 2
      component_markdown.go
  6. 22 0
      component_text.go
  7. 30 10
      component_web_container.go
  8. 2 1
      component_web_helper.go
  9. 6 0
      component_web_image.go
  10. 8 6
      component_web_markdown.go
  11. 76 0
      component_web_text.go
  12. 28 8
      css.go
  13. 1 0
      factory.go
  14. 30 2
      factory_web.go
  15. 2 2
      go.mod
  16. 30 2
      markdown.go
  17. 5 4
      style.go
  18. 5 0
      style_background.go
  19. 1 0
      style_block.go
  20. 37 0
      style_color.go
  21. 8 7
      style_font.go
  22. 6 9
      tree_node.go
  23. 1 1
      tree_router.go
  24. 1 1
      uv_context.go

+ 7 - 0
component_button.go

@@ -19,3 +19,10 @@ type ButtonComponent interface {
 	SetValue(format string, values []Value)
 	SetHandler(handler ClickHandler)
 }
+
+func ButtonComponentModelLabel(label string) ButtonComponentModel {
+	return ButtonComponentModel{
+		Format: label,
+		Values: []Value{},
+	}
+}

+ 1 - 0
component_code.go

@@ -12,4 +12,5 @@ type CodeComponentOptions struct {
 
 type CodeComponent interface {
 	SetCode(language string, content string)
+	SetHandler(handler ClickHandler)
 }

+ 12 - 1
component_container.go

@@ -2,9 +2,20 @@
 
 package ultraviolet
 
+type ContainerComponentModel struct {
+	Value Value
+}
+
+type ContainerComponentController struct {
+	Handler ClickHandler
+}
+
 type ContainerComponentOptions struct {
-	View View
+	View       View
+	Model      ContainerComponentModel
+	Controller ContainerComponentController
 }
 
 type ContainerComponent interface {
+	SetValue(value Value)
 }

+ 3 - 1
component_image.go

@@ -1,7 +1,8 @@
 package ultraviolet
 
 type ImageComponentModel struct {
-	Path string
+	Path  string
+	Width int
 }
 
 type ImageComponentOptions struct {
@@ -11,4 +12,5 @@ type ImageComponentOptions struct {
 
 type ImageComponent interface {
 	SetPath(path string)
+	SetWidth(width int)
 }

+ 3 - 2
component_markdown.go

@@ -1,7 +1,8 @@
 package ultraviolet
 
 type MarkdownComponentModel struct {
-	Content string
+	Content   string
+	Paragraph bool
 }
 
 type MarkdownComponentOptions struct {
@@ -10,5 +11,5 @@ type MarkdownComponentOptions struct {
 }
 
 type MarkdownComponent interface {
-	SetMarkdown(content string)
+	SetMarkdown(content string, paragraph bool)
 }

+ 22 - 0
component_text.go

@@ -0,0 +1,22 @@
+package ultraviolet
+
+type TextComponentModel struct {
+	Format string
+	Values []Value
+}
+
+type TextComponentOptions struct {
+	View  View
+	Model TextComponentModel
+}
+
+type TextComponent interface {
+	SetValue(format string, values []Value)
+}
+
+func TextComponentModelLabel(label string) TextComponentModel {
+	return TextComponentModel{
+		Format: label,
+		Values: []Value{},
+	}
+}

+ 30 - 10
component_web_container.go

@@ -7,15 +7,15 @@ import (
 )
 
 type ContainerComponentWeb struct {
-	node   js.Value
-	view   View
-	change bool
+	node    js.Value
+	view    View
+	value   Value
+	handler ClickHandler
 }
 
 func NewContainerComponentWeb(view View) *ContainerComponentWeb {
 	return &ContainerComponentWeb{
-		view:   view,
-		change: true,
+		view: view,
 	}
 }
 
@@ -28,19 +28,29 @@ func (this *ContainerComponentWeb) Build(parent Component) {
 		this.view,
 		"",
 		"",
-		AbstractHandlers{},
+		AbstractHandlers{
+			HANDLER_CLICK: func(args ...any) error {
+				if this.handler == nil {
+					return nil
+				}
+
+				this.handler()
+				return nil
+			},
+		},
 	)
 }
 
 func (this *ContainerComponentWeb) Invalidate() bool {
-	result := this.change
-	this.change = false
-	return result
+	if this.value == nil {
+		return false
+	}
+
+	return this.value.Invalidate()
 }
 
 func (this *ContainerComponentWeb) Clean() {
 	WebCleanChildren(this.node)
-	this.change = true
 }
 
 // JS
@@ -48,3 +58,13 @@ func (this *ContainerComponentWeb) Clean() {
 func (this *ContainerComponentWeb) Node() js.Value {
 	return this.node
 }
+
+// ComponentContainer
+
+func (this *ContainerComponentWeb) SetValue(value Value) {
+	this.value = value
+}
+
+func (this *ContainerComponentWeb) SetHandler(handler ClickHandler) {
+	this.handler = handler
+}

+ 2 - 1
component_web_helper.go

@@ -78,12 +78,13 @@ func WebBuildDivInternal(oldNode js.Value, parent js.Value, view View, text stri
 	return newNode
 }
 
-func WebBuildImg(oldNode js.Value, parentComponent Component, view View, path string) js.Value {
+func WebBuildImg(oldNode js.Value, parentComponent Component, view View, path string, width int) js.Value {
 	parent := parentComponent.(JS).Node()
 
 	newNode := WebDocument().Call("createElement", "img")
 	newNode.Call("setAttribute", "src", path)
 	newNode.Call("setAttribute", "style", ViewToString(view))
+	newNode.Call("setAttribute", "width", width)
 
 	if parent.Call("contains", oldNode).Bool() {
 		parent.Call(

+ 6 - 0
component_web_image.go

@@ -11,6 +11,7 @@ type ImageComponentWeb struct {
 	view   View
 	change bool
 	path   string
+	width  int
 }
 
 func NewImageComponentWeb(view View) *ImageComponentWeb {
@@ -29,6 +30,7 @@ func (this *ImageComponentWeb) Build(parent Component) {
 		parent,
 		this.view,
 		this.path,
+		this.width,
 	)
 }
 
@@ -53,3 +55,7 @@ func (this *ImageComponentWeb) Node() js.Value {
 func (this *ImageComponentWeb) SetPath(path string) {
 	this.path = path
 }
+
+func (this *ImageComponentWeb) SetWidth(width int) {
+	this.width = width
+}

+ 8 - 6
component_web_markdown.go

@@ -7,10 +7,11 @@ import (
 )
 
 type MarkdownComponentWeb struct {
-	node    js.Value
-	view    View
-	change  bool
-	content string
+	node      js.Value
+	view      View
+	change    bool
+	content   string
+	paragraph bool
 }
 
 func NewMarkdownComponentWeb(view View) *MarkdownComponentWeb {
@@ -28,7 +29,7 @@ func (this *MarkdownComponentWeb) Build(parent Component) {
 		parent,
 		this.view,
 		"",
-		MarkdownConvert(this.content),
+		MarkdownConvert(this.content, this.paragraph),
 		AbstractHandlers{},
 	)
 }
@@ -52,6 +53,7 @@ func (this *MarkdownComponentWeb) Node() js.Value {
 
 // ComponentMarkdown
 
-func (this *MarkdownComponentWeb) SetMarkdown(content string) {
+func (this *MarkdownComponentWeb) SetMarkdown(content string, paragraph bool) {
 	this.content = content
+	this.paragraph = paragraph
 }

+ 76 - 0
component_web_text.go

@@ -0,0 +1,76 @@
+//go:build js && wasm
+
+package ultraviolet
+
+import (
+	"fmt"
+	"syscall/js"
+)
+
+type TextComponentWeb struct {
+	node   js.Value
+	view   View
+	format string
+	values []Value
+}
+
+func NewTextComponentWeb(view View) *TextComponentWeb {
+	return &TextComponentWeb{
+		view:   view,
+		values: []Value{},
+	}
+}
+
+// Component
+
+func (this *TextComponentWeb) Build(parent Component) {
+	values := []any{}
+	for _, text := range this.values {
+		if text.Value() == nil {
+			values = append(values, "")
+		} else {
+			values = append(values, text.Value().(string))
+		}
+	}
+
+	this.node = WebBuildDiv(
+		this.node,
+		parent,
+		this.view,
+		"",
+		fmt.Sprintf(
+			this.format,
+			values...,
+		),
+		AbstractHandlers{},
+	)
+}
+
+func (this *TextComponentWeb) Invalidate() bool {
+	for _, value := range this.values {
+		if !value.Invalidate() {
+			continue
+		}
+
+		return true
+	}
+
+	return false
+}
+
+func (this *TextComponentWeb) Clean() {
+	WebCleanChildren(this.node)
+}
+
+// JS
+
+func (this *TextComponentWeb) Node() js.Value {
+	return this.node
+}
+
+// ComponentText
+
+func (this *TextComponentWeb) SetValue(format string, values []Value) {
+	this.format = format
+	this.values = values
+}

+ 28 - 8
css.go

@@ -20,6 +20,8 @@ func ViewToString(view View) string {
 		PaddingToString(view.Block.Padding),
 		BorderThicknessToString(view.Block.Border),
 		BorderRadiusToString(view.Block.Border),
+		BorderRadiusToString(view.Block.Border),
+		BackgroundToString(view.Background),
 		FontToString(view.Font),
 		view.Custom,
 	}
@@ -119,6 +121,10 @@ func RightToString(right int) string {
 }
 
 func MarginToString(margin Margin) string {
+	if margin.Center {
+		return "margin: 0 auto;"
+	}
+
 	if margin.Top == 0 && margin.Right == 0 && margin.Bottom == 0 && margin.Left == 0 {
 		return ""
 	}
@@ -195,6 +201,14 @@ func BorderRadiusToString(border Border) string {
 	)
 }
 
+func BackgroundToString(background Background) string {
+	if background.Color.Alpha == 0 {
+		return ""
+	}
+
+	return fmt.Sprintf("background-color: %s;", ColorToString(background.Color))
+}
+
 func ColorToString(color Color) string {
 	if color.Alpha == 0 {
 		return ""
@@ -212,10 +226,10 @@ func ColorToString(color Color) string {
 func FontToString(font Font) string {
 	parts := []string{}
 
-	if font.Face != "" {
+	if font.Family != "" {
 		parts = append(
 			parts,
-			fmt.Sprintf("font-face: '%s';", font.Face),
+			fmt.Sprintf("font-family: '%s';", font.Family),
 		)
 	}
 
@@ -226,6 +240,13 @@ func FontToString(font Font) string {
 		)
 	}
 
+	if font.Uppercase {
+		parts = append(
+			parts,
+			"text-transform: uppercase;",
+		)
+	}
+
 	if font.Bold {
 		parts = append(
 			parts,
@@ -248,19 +269,18 @@ func FontToString(font Font) string {
 		)
 	}
 
-	fontColor := ColorToString(font.FontColor)
-	if fontColor != "" {
+	if font.LetterSpacing != 0 {
 		parts = append(
 			parts,
-			fmt.Sprintf("color: %s;", ColorToString(font.FontColor)),
+			fmt.Sprintf("letter-spacing: %dpx;", font.LetterSpacing),
 		)
 	}
 
-	backgroundColor := ColorToString(font.BackgroundColor)
-	if backgroundColor != "" {
+	fontColor := ColorToString(font.Color)
+	if fontColor != "" {
 		parts = append(
 			parts,
-			fmt.Sprintf("background-color: %s;", ColorToString(font.BackgroundColor)),
+			fmt.Sprintf("color: %s;", ColorToString(font.Color)),
 		)
 	}
 

+ 1 - 0
factory.go

@@ -14,6 +14,7 @@ type ComponentOptions struct {
 type Factory interface {
 	NewRoot(options any) Component
 	NewContainer(options any) Component
+	NewText(options any) Component
 	NewButton(options any) Component
 	NewImage(options any) Component
 	NewMarkdown(options any) Component

+ 30 - 2
factory_web.go

@@ -16,7 +16,31 @@ func (this *WebFactory) NewRoot(options any) Component {
 
 func (this *WebFactory) NewContainer(options any) Component {
 	optionsContainer := options.(ContainerComponentOptions)
-	return NewContainerComponentWeb(optionsContainer.View)
+	result := NewContainerComponentWeb(optionsContainer.View)
+
+	if optionsContainer.Controller.Handler != nil {
+		result.SetHandler(optionsContainer.Controller.Handler)
+	}
+
+	if optionsContainer.Model.Value != nil {
+		result.SetValue(optionsContainer.Model.Value)
+	}
+
+	return result
+}
+
+func (this *WebFactory) NewText(options any) Component {
+	optionsText := options.(TextComponentOptions)
+	result := NewTextComponentWeb(optionsText.View)
+
+	if optionsText.Model.Format != "" && optionsText.Model.Values != nil {
+		result.SetValue(
+			optionsText.Model.Format,
+			optionsText.Model.Values,
+		)
+	}
+
+	return result
 }
 
 func (this *WebFactory) NewButton(options any) Component {
@@ -45,6 +69,10 @@ func (this *WebFactory) NewImage(options any) Component {
 		result.SetPath(optionsImage.Model.Path)
 	}
 
+	if optionsImage.Model.Width != 0 {
+		result.SetWidth(optionsImage.Model.Width)
+	}
+
 	return result
 }
 
@@ -53,7 +81,7 @@ func (this *WebFactory) NewMarkdown(options any) Component {
 	result := NewMarkdownComponentWeb(optionsMarkdown.View)
 
 	if optionsMarkdown.Model.Content != "" {
-		result.SetMarkdown(optionsMarkdown.Model.Content)
+		result.SetMarkdown(optionsMarkdown.Model.Content, optionsMarkdown.Model.Paragraph)
 	}
 
 	return result

+ 2 - 2
go.mod

@@ -3,8 +3,8 @@ module git.buran.team/ultraviolet
 go 1.26.0
 
 require (
-	git.buran.team/fairwind v0.0.0
+	git.buran.team/main/fairwind v0.0.0
 	github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
 )
 
-replace git.buran.team/fairwind v0.0.0 => ../fairwind
+replace git.buran.team/main/fairwind v0.0.0 => ../fairwind

+ 30 - 2
markdown.go

@@ -1,22 +1,50 @@
 package ultraviolet
 
 import (
+	"io"
+
 	"github.com/gomarkdown/markdown"
+	"github.com/gomarkdown/markdown/ast"
 	"github.com/gomarkdown/markdown/html"
 	"github.com/gomarkdown/markdown/parser"
 )
 
-func MarkdownConvert(md string) string {
+func MarkdownConvert(content string, paragraph bool) string {
 	return string(
 		markdown.Render(
 			parser.NewWithExtensions(
 				parser.CommonExtensions|parser.AutoHeadingIDs|parser.NoEmptyLineBeforeBlock,
 			).Parse(
-				[]byte(md),
+				[]byte(content),
 			),
 			html.NewRenderer(
 				html.RendererOptions{
 					Flags: html.CommonFlags | html.HrefTargetBlank,
+					RenderNodeHook: func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
+						if paragraph {
+							_, ok := node.(*ast.Paragraph)
+							if ok {
+								if entering {
+									io.WriteString(w, "<div>")
+								} else {
+									io.WriteString(w, "</div>")
+								}
+
+								return ast.GoToNext, true
+							}
+						}
+
+						codeBlock, ok := node.(*ast.CodeBlock)
+						if !ok {
+							return ast.GoToNext, false
+						}
+
+						if entering {
+							w.Write(codeBlock.Literal)
+						}
+
+						return ast.GoToNext, true
+					},
 				},
 			),
 		),

+ 5 - 4
style.go

@@ -3,8 +3,9 @@ package ultraviolet
 const CURSOR_POINTER = 1
 
 type View struct {
-	Block  Block
-	Font   Font
-	Cursor int
-	Custom string
+	Block      Block
+	Background Background
+	Font       Font
+	Cursor     int
+	Custom     string
 }

+ 5 - 0
style_background.go

@@ -0,0 +1,5 @@
+package ultraviolet
+
+type Background struct {
+	Color Color
+}

+ 1 - 0
style_block.go

@@ -16,6 +16,7 @@ type Margin struct {
 	Left   int
 	Top    int
 	Right  int
+	Center bool
 }
 
 type Padding struct {

+ 37 - 0
style_color.go

@@ -1,8 +1,45 @@
 package ultraviolet
 
+import (
+	"strconv"
+	"strings"
+)
+
 type Color struct {
 	Red   int
 	Green int
 	Blue  int
 	Alpha int
 }
+
+func ColorFromHex(value string) Color {
+	if len(value) != 8 {
+		return Color{}
+	}
+
+	if !strings.HasPrefix(value, "0x") {
+		return Color{}
+	}
+
+	red, err := strconv.ParseInt(value[2:4], 16, 16)
+	if err != nil {
+		return Color{}
+	}
+
+	green, err := strconv.ParseInt(value[4:6], 16, 16)
+	if err != nil {
+		return Color{}
+	}
+
+	blue, err := strconv.ParseInt(value[6:8], 16, 16)
+	if err != nil {
+		return Color{}
+	}
+
+	return Color{
+		Red:   int(red),
+		Green: int(green),
+		Blue:  int(blue),
+		Alpha: 255,
+	}
+}

+ 8 - 7
style_font.go

@@ -3,11 +3,12 @@ package ultraviolet
 const DECORATION_UNDERLINE = 1
 
 type Font struct {
-	Face            string
-	Size            int
-	Bold            bool
-	Italic          bool
-	Decoration      int
-	FontColor       Color
-	BackgroundColor Color
+	Family        string
+	Size          int
+	Uppercase     bool
+	Bold          bool
+	Italic        bool
+	Decoration    int
+	LetterSpacing int
+	Color         Color
 }

+ 6 - 9
tree_node.go

@@ -14,13 +14,6 @@ type Node struct {
 	Children  NodeList
 }
 
-func NewNode(component Component) *Node {
-	return &Node{
-		Children:  NodeList{},
-		Component: component,
-	}
-}
-
 func (this *Node) Build(node *Node) {
 	this.mutex.Lock()
 	defer this.mutex.Unlock()
@@ -45,7 +38,11 @@ func (this *Node) update(parent Component, force bool) {
 		force = true
 	}
 
-	for _, item := range this.Children {
-		item.update(this.Component, force)
+	for _, child := range this.Children {
+		child.update(this.Component, force)
 	}
 }
+
+func NodeListBuild(list NodeList, nodes ...*Node) NodeList {
+	return append(list, nodes...)
+}

+ 1 - 1
tree_router.go

@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"sync"
 
-	fw "git.buran.team/fairwind"
+	fw "git.buran.team/main/fairwind"
 )
 
 type Parameters map[string]any

+ 1 - 1
uv_context.go

@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"sync"
 
-	fw "git.buran.team/fairwind"
+	fw "git.buran.team/main/fairwind"
 )
 
 type UVContext struct {