mirror of https://github.com/anoshenko/rui.git
Initialization
This commit is contained in:
parent
bdb490c953
commit
73e7184395
|
@ -0,0 +1,14 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
.DS_Store
|
||||
demo/__debug_bin
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceRoot}/demo",
|
||||
//"program": "${workspaceRoot}/editor",
|
||||
"env": {},
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"anoshenko",
|
||||
"helvetica",
|
||||
"htmlid",
|
||||
"nesw",
|
||||
"nwse",
|
||||
"onclick",
|
||||
"onkeydown",
|
||||
"onmousedown",
|
||||
"upgrader"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,40 @@
|
|||
package rui
|
||||
|
||||
import "strings"
|
||||
|
||||
// AbsoluteLayout - list-container of View
|
||||
type AbsoluteLayout interface {
|
||||
ViewsContainer
|
||||
}
|
||||
|
||||
type absoluteLayoutData struct {
|
||||
viewsContainerData
|
||||
}
|
||||
|
||||
// NewAbsoluteLayout create new AbsoluteLayout object and return it
|
||||
func NewAbsoluteLayout(session Session, params Params) AbsoluteLayout {
|
||||
view := new(absoluteLayoutData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newAbsoluteLayout(session Session) View {
|
||||
return NewAbsoluteLayout(session, nil)
|
||||
}
|
||||
|
||||
// Init initialize fields of ViewsContainer by default values
|
||||
func (layout *absoluteLayoutData) Init(session Session) {
|
||||
layout.viewsContainerData.Init(session)
|
||||
layout.tag = "AbsoluteLayout"
|
||||
layout.systemClass = "ruiAbsoluteLayout"
|
||||
}
|
||||
|
||||
func (layout *absoluteLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
if layout.views != nil {
|
||||
for _, view := range layout.views {
|
||||
view.addToCSSStyle(map[string]string{`position`: `absolute`})
|
||||
viewHTML(view, buffer)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AngleUnitType : type of enumerated constants for define a type of AngleUnit value.
|
||||
// Can take the following values: Radian, Degree, Gradian, and Turn
|
||||
type AngleUnitType uint8
|
||||
|
||||
const (
|
||||
// Radian - angle in radians
|
||||
Radian AngleUnitType = 0
|
||||
// Radian - angle in radians * π
|
||||
PiRadian AngleUnitType = 1
|
||||
// Degree - angle in degrees
|
||||
Degree AngleUnitType = 2
|
||||
// Gradian - angle in gradian (1⁄400 of a full circle)
|
||||
Gradian AngleUnitType = 3
|
||||
// Turn - angle in turns (1 turn = 360 degree)
|
||||
Turn AngleUnitType = 4
|
||||
)
|
||||
|
||||
// AngleUnit describe a size (Value field) and size unit (Type field).
|
||||
type AngleUnit struct {
|
||||
Type AngleUnitType
|
||||
Value float64
|
||||
}
|
||||
|
||||
// Deg creates AngleUnit with Degree type
|
||||
func Deg(value float64) AngleUnit {
|
||||
return AngleUnit{Type: Degree, Value: value}
|
||||
}
|
||||
|
||||
// Rad create AngleUnit with Radian type
|
||||
func Rad(value float64) AngleUnit {
|
||||
return AngleUnit{Type: Radian, Value: value}
|
||||
}
|
||||
|
||||
// PiRad create AngleUnit with PiRadian type
|
||||
func PiRad(value float64) AngleUnit {
|
||||
return AngleUnit{Type: PiRadian, Value: value}
|
||||
}
|
||||
|
||||
// Grad create AngleUnit with Gradian type
|
||||
func Grad(value float64) AngleUnit {
|
||||
return AngleUnit{Type: Gradian, Value: value}
|
||||
}
|
||||
|
||||
// Equal compare two AngleUnit. Return true if AngleUnit are equal
|
||||
func (angle AngleUnit) Equal(size2 AngleUnit) bool {
|
||||
return angle.Type == size2.Type && angle.Value == size2.Value
|
||||
}
|
||||
|
||||
func angleUnitSuffixes() map[AngleUnitType]string {
|
||||
return map[AngleUnitType]string{
|
||||
Degree: "deg",
|
||||
Radian: "rad",
|
||||
PiRadian: "pi",
|
||||
Gradian: "grad",
|
||||
Turn: "turn",
|
||||
}
|
||||
}
|
||||
|
||||
// StringToAngleUnit converts the string argument to AngleUnit
|
||||
func StringToAngleUnit(value string) (AngleUnit, bool) {
|
||||
var angle AngleUnit
|
||||
ok, err := angle.setValue(value)
|
||||
if !ok {
|
||||
ErrorLog(err)
|
||||
}
|
||||
return angle, ok
|
||||
}
|
||||
|
||||
func (angle *AngleUnit) setValue(value string) (bool, string) {
|
||||
value = strings.ToLower(strings.Trim(value, " \t\n\r"))
|
||||
|
||||
setValue := func(suffix string, unitType AngleUnitType) (bool, string) {
|
||||
val, err := strconv.ParseFloat(value[:len(value)-len(suffix)], 64)
|
||||
if err != nil {
|
||||
return false, `AngleUnit.SetValue("` + value + `") error: ` + err.Error()
|
||||
}
|
||||
angle.Value = val
|
||||
angle.Type = unitType
|
||||
return true, ""
|
||||
}
|
||||
|
||||
if value == "π" {
|
||||
angle.Value = 1
|
||||
angle.Type = PiRadian
|
||||
return true, ""
|
||||
}
|
||||
|
||||
if strings.HasSuffix(value, "π") {
|
||||
return setValue("π", PiRadian)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(value, "°") {
|
||||
return setValue("°", Degree)
|
||||
}
|
||||
|
||||
for unitType, suffix := range angleUnitSuffixes() {
|
||||
if strings.HasSuffix(value, suffix) {
|
||||
return setValue(suffix, unitType)
|
||||
}
|
||||
}
|
||||
|
||||
if val, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
angle.Value = val
|
||||
angle.Type = Radian
|
||||
return true, ""
|
||||
}
|
||||
|
||||
return false, `AngleUnit.SetValue("` + value + `") error: invalid argument`
|
||||
}
|
||||
|
||||
// String - convert AngleUnit to string
|
||||
func (angle AngleUnit) String() string {
|
||||
if suffix, ok := angleUnitSuffixes()[angle.Type]; ok {
|
||||
return fmt.Sprintf("%g%s", angle.Value, suffix)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%g", angle.Value)
|
||||
}
|
||||
|
||||
// cssString - convert AngleUnit to string
|
||||
func (angle AngleUnit) cssString() string {
|
||||
if angle.Type == PiRadian {
|
||||
return fmt.Sprintf("%grad", angle.Value*math.Pi)
|
||||
}
|
||||
|
||||
return angle.String()
|
||||
}
|
||||
|
||||
// ToDegree returns the angle in radians
|
||||
func (angle AngleUnit) ToRadian() AngleUnit {
|
||||
switch angle.Type {
|
||||
case PiRadian:
|
||||
return AngleUnit{Value: angle.Value * math.Pi, Type: Radian}
|
||||
|
||||
case Degree:
|
||||
return AngleUnit{Value: angle.Value * math.Pi / 180, Type: Radian}
|
||||
|
||||
case Gradian:
|
||||
return AngleUnit{Value: angle.Value * math.Pi / 200, Type: Radian}
|
||||
|
||||
case Turn:
|
||||
return AngleUnit{Value: angle.Value * 2 * math.Pi, Type: Radian}
|
||||
}
|
||||
|
||||
return angle
|
||||
}
|
||||
|
||||
// ToDegree returns the angle in degrees
|
||||
func (angle AngleUnit) ToDegree() AngleUnit {
|
||||
switch angle.Type {
|
||||
case Radian:
|
||||
return AngleUnit{Value: angle.Value * 180 / math.Pi, Type: Degree}
|
||||
|
||||
case PiRadian:
|
||||
return AngleUnit{Value: angle.Value * 180, Type: Degree}
|
||||
|
||||
case Gradian:
|
||||
return AngleUnit{Value: angle.Value * 360 / 400, Type: Degree}
|
||||
|
||||
case Turn:
|
||||
return AngleUnit{Value: angle.Value * 360, Type: Degree}
|
||||
}
|
||||
|
||||
return angle
|
||||
}
|
||||
|
||||
// ToGradian returns the angle in gradians (1⁄400 of a full circle)
|
||||
func (angle AngleUnit) ToGradian() AngleUnit {
|
||||
switch angle.Type {
|
||||
case Radian:
|
||||
return AngleUnit{Value: angle.Value * 200 / math.Pi, Type: Gradian}
|
||||
|
||||
case PiRadian:
|
||||
return AngleUnit{Value: angle.Value * 200, Type: Gradian}
|
||||
|
||||
case Degree:
|
||||
return AngleUnit{Value: angle.Value * 400 / 360, Type: Gradian}
|
||||
|
||||
case Turn:
|
||||
return AngleUnit{Value: angle.Value * 400, Type: Gradian}
|
||||
}
|
||||
|
||||
return angle
|
||||
}
|
||||
|
||||
// ToTurn returns the angle in turns (1 turn = 360 degree)
|
||||
func (angle AngleUnit) ToTurn() AngleUnit {
|
||||
switch angle.Type {
|
||||
case Radian:
|
||||
return AngleUnit{Value: angle.Value / (2 * math.Pi), Type: Turn}
|
||||
|
||||
case PiRadian:
|
||||
return AngleUnit{Value: angle.Value / 2, Type: Turn}
|
||||
|
||||
case Degree:
|
||||
return AngleUnit{Value: angle.Value / 360, Type: Turn}
|
||||
|
||||
case Gradian:
|
||||
return AngleUnit{Value: angle.Value / 400, Type: Turn}
|
||||
}
|
||||
|
||||
return angle
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
package rui
|
||||
|
||||
/*
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type AnimationTags struct {
|
||||
Tag string
|
||||
Start, End interface{}
|
||||
}
|
||||
|
||||
type AnimationKeyFrame struct {
|
||||
KeyFrame int
|
||||
TimingFunction string
|
||||
Params Params
|
||||
}
|
||||
|
||||
type AnimationScenario interface {
|
||||
fmt.Stringer
|
||||
ruiStringer
|
||||
Name() string
|
||||
cssString(session Session) string
|
||||
}
|
||||
|
||||
type animationScenario struct {
|
||||
name string
|
||||
tags []AnimationTags
|
||||
keyFrames []AnimationKeyFrame
|
||||
cssText string
|
||||
}
|
||||
|
||||
var animationScenarios = []string{}
|
||||
|
||||
func addAnimationScenario(name string) string {
|
||||
animationScenarios = append(animationScenarios, name)
|
||||
return name
|
||||
}
|
||||
|
||||
func registerAnimationScenario() string {
|
||||
find := func(text string) bool {
|
||||
for _, scenario := range animationScenarios {
|
||||
if scenario == text {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
n := 1
|
||||
name := fmt.Sprintf("scenario%08d", n)
|
||||
for find(name) {
|
||||
n++
|
||||
name = fmt.Sprintf("scenario%08d", n)
|
||||
}
|
||||
|
||||
animationScenarios = append(animationScenarios, name)
|
||||
return name
|
||||
}
|
||||
|
||||
func NewAnimationScenario(tags []AnimationTags, keyFrames []AnimationKeyFrame) AnimationScenario {
|
||||
if tags == nil {
|
||||
ErrorLog(`Nil "tags" argument is not allowed.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
ErrorLog(`An empty "tags" argument is not allowed.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
animation := new(animationScenario)
|
||||
animation.tags = tags
|
||||
if keyFrames == nil && len(keyFrames) > 0 {
|
||||
animation.keyFrames = keyFrames
|
||||
}
|
||||
animation.name = registerAnimationScenario()
|
||||
|
||||
return animation
|
||||
}
|
||||
|
||||
func (animation *animationScenario) Name() string {
|
||||
return animation.name
|
||||
}
|
||||
|
||||
func (animation *animationScenario) String() string {
|
||||
writer := newRUIWriter()
|
||||
animation.ruiString(writer)
|
||||
return writer.finish()
|
||||
}
|
||||
|
||||
func (animation *animationScenario) ruiString(writer ruiWriter) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func valueToCSS(tag string, value interface{}, session Session) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
convertFloat := func(val float64) string {
|
||||
if _, ok := sizeProperties[tag]; ok {
|
||||
return fmt.Sprintf("%gpx", val)
|
||||
}
|
||||
return fmt.Sprintf("%g", val)
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
value, ok := session.resolveConstants(value)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if _, ok := sizeProperties[tag]; ok {
|
||||
var size SizeUnit
|
||||
if size.SetValue(value) {
|
||||
return size.cssString("auto")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if isPropertyInList(tag, colorProperties) {
|
||||
var color Color
|
||||
if color.SetValue(value) {
|
||||
return color.cssString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if isPropertyInList(tag, angleProperties) {
|
||||
var angle AngleUnit
|
||||
if angle.SetValue(value) {
|
||||
return angle.cssString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if _, ok := enumProperties[tag]; ok {
|
||||
var size SizeUnit
|
||||
if size.SetValue(value) {
|
||||
return size.cssString("auto")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return value
|
||||
|
||||
case SizeUnit:
|
||||
return value.cssString("auto")
|
||||
|
||||
case AngleUnit:
|
||||
return value.cssString()
|
||||
|
||||
case Color:
|
||||
return value.cssString()
|
||||
|
||||
case float32:
|
||||
return convertFloat(float64(value))
|
||||
|
||||
case float64:
|
||||
return convertFloat(value)
|
||||
|
||||
default:
|
||||
if n, ok := isInt(value); ok {
|
||||
if prop, ok := enumProperties[tag]; ok {
|
||||
values := prop.cssValues
|
||||
if n >= 0 && n < len(values) {
|
||||
return values[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
return convertFloat(float64(n))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (animation *animationScenario) cssString(session Session) string {
|
||||
if animation.cssText != "" {
|
||||
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
writeValue := func(tag string, value interface{}) {
|
||||
if cssValue := valueToCSS(tag, value); cssValue != "" {
|
||||
buffer.WriteString(" ")
|
||||
buffer.WriteString(tag)
|
||||
buffer.WriteString(": ")
|
||||
buffer.WriteString(cssValue)
|
||||
buffer.WriteString(";\n")
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString(`@keyframes `)
|
||||
buffer.WriteString(animation.name)
|
||||
|
||||
buffer.WriteString(" {\n from {\n")
|
||||
for _, property := range animation.tags {
|
||||
writeValue(property.Tag, property.Start)
|
||||
}
|
||||
|
||||
buffer.WriteString(" }\n to {\n")
|
||||
for _, property := range animation.tags {
|
||||
writeValue(property.Tag, property.End)
|
||||
}
|
||||
buffer.WriteString(" }\n")
|
||||
|
||||
if animation.keyFrames != nil {
|
||||
for _, keyFrame := range animation.keyFrames {
|
||||
if keyFrame.KeyFrame > 0 && keyFrame.KeyFrame < 100 &&
|
||||
keyFrame.Params != nil && len(keyFrame.Params) > 0 {
|
||||
|
||||
buffer.WriteString(" ")
|
||||
buffer.WriteString(strconv.Itoa(keyFrame.KeyFrame))
|
||||
buffer.WriteString("% {\n")
|
||||
for tag, value := range keyFrame.Params {
|
||||
writeValue(tag, value)
|
||||
}
|
||||
buffer.WriteString(" }\n")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer.WriteString("}\n")
|
||||
|
||||
animation.cssText = buffer.String()
|
||||
}
|
||||
|
||||
return animation.cssText
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,74 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// ProtocolInDebugLog If it is set to true, then the protocol of the exchange between
|
||||
// clients and the server is displayed in the debug log
|
||||
var ProtocolInDebugLog = false
|
||||
|
||||
var debugLogFunc func(string) = func(text string) {
|
||||
log.Println("\033[34m" + text)
|
||||
}
|
||||
|
||||
var errorLogFunc = func(text string) {
|
||||
log.Println("\033[31m" + text)
|
||||
//println(text)
|
||||
}
|
||||
|
||||
// SetDebugLog sets a function for outputting debug info.
|
||||
// The default value is nil (debug info is ignored)
|
||||
func SetDebugLog(f func(string)) {
|
||||
debugLogFunc = f
|
||||
}
|
||||
|
||||
// SetErrorLog sets a function for outputting error messages.
|
||||
// The default value is log.Println(text)
|
||||
func SetErrorLog(f func(string)) {
|
||||
errorLogFunc = f
|
||||
}
|
||||
|
||||
// DebugLog print the text to the debug log
|
||||
func DebugLog(text string) {
|
||||
if debugLogFunc != nil {
|
||||
debugLogFunc(text)
|
||||
}
|
||||
}
|
||||
|
||||
// DebugLogF print the text to the debug log
|
||||
func DebugLogF(format string, a ...interface{}) {
|
||||
if debugLogFunc != nil {
|
||||
debugLogFunc(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorLog print the text to the error log
|
||||
func ErrorLog(text string) {
|
||||
if errorLogFunc != nil {
|
||||
errorLogFunc(text)
|
||||
errorStack()
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorLogF print the text to the error log
|
||||
func ErrorLogF(format string, a ...interface{}) {
|
||||
if errorLogFunc != nil {
|
||||
errorLogFunc(fmt.Sprintf(format, a...))
|
||||
errorStack()
|
||||
}
|
||||
}
|
||||
|
||||
func errorStack() {
|
||||
if errorLogFunc != nil {
|
||||
skip := 2
|
||||
_, file, line, ok := runtime.Caller(skip)
|
||||
for ok {
|
||||
errorLogFunc(fmt.Sprintf("\t%s: line %d", file, line))
|
||||
skip++
|
||||
_, file, line, ok = runtime.Caller(skip)
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,124 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
min-width: 1px;
|
||||
min-height: 1px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
div {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
ul:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.ruiRoot {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.ruiPopupLayer {
|
||||
background-color: rgba(128,128,128,0.1);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.ruiView {
|
||||
}
|
||||
|
||||
.ruiAbsoluteLayout {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ruiGridLayout {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.ruiListLayout {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ruiStackLayout {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.ruiStackPageLayout {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 2;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 2;
|
||||
}
|
||||
|
||||
.ruiTabsLayout {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.ruiImageView {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.ruiListView {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-content: stretch;
|
||||
}
|
||||
/*
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background: #FFF;
|
||||
color: #000;
|
||||
}
|
||||
.ruiRoot {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #303030;
|
||||
color: #F0F0F0;
|
||||
}
|
||||
.ruiRoot {
|
||||
background-color: #303030;
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,297 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//go:embed app_scripts.js
|
||||
var defaultScripts string
|
||||
|
||||
//go:embed app_styles.css
|
||||
var appStyles string
|
||||
|
||||
//go:embed defaultTheme.rui
|
||||
var defaultThemeText string
|
||||
|
||||
// Application - app interface
|
||||
type Application interface {
|
||||
// Start - start the application life cycle
|
||||
Start(addr string)
|
||||
Finish()
|
||||
nextSessionID() int
|
||||
removeSession(id int)
|
||||
}
|
||||
|
||||
type application struct {
|
||||
name, icon string
|
||||
createContentFunc func(Session) SessionContent
|
||||
sessions map[int]Session
|
||||
}
|
||||
|
||||
func (app *application) getStartPage() string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
buffer.WriteString(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>`)
|
||||
buffer.WriteString(app.name)
|
||||
buffer.WriteString("</title>")
|
||||
if app.icon != "" {
|
||||
buffer.WriteString(`
|
||||
<link rel="icon" href="`)
|
||||
buffer.WriteString(app.icon)
|
||||
buffer.WriteString(`">`)
|
||||
}
|
||||
|
||||
buffer.WriteString(`
|
||||
<base target="_blank" rel="noopener">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<style>`)
|
||||
buffer.WriteString(appStyles)
|
||||
buffer.WriteString(`</style>
|
||||
<script>`)
|
||||
buffer.WriteString(defaultScripts)
|
||||
buffer.WriteString(`</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ruiRoot" id="ruiRootView"></div>
|
||||
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
|
||||
</body>
|
||||
</html>`)
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (app *application) init(name, icon string) {
|
||||
app.name = name
|
||||
app.icon = icon
|
||||
app.sessions = map[int]Session{}
|
||||
}
|
||||
|
||||
func (app *application) Start(addr string) {
|
||||
http.Handle("/", app)
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
}
|
||||
|
||||
func (app *application) Finish() {
|
||||
for _, session := range app.sessions {
|
||||
session.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (app *application) nextSessionID() int {
|
||||
n := rand.Intn(0x7FFFFFFE) + 1
|
||||
_, ok := app.sessions[n]
|
||||
for ok {
|
||||
n = rand.Intn(0x7FFFFFFE) + 1
|
||||
_, ok = app.sessions[n]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (app *application) removeSession(id int) {
|
||||
delete(app.sessions, id)
|
||||
}
|
||||
|
||||
func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLogF("%s %s", req.Method, req.URL.Path)
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
switch req.URL.Path {
|
||||
case "/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, app.getStartPage())
|
||||
|
||||
case "/ws":
|
||||
if brige := CreateSocketBrige(w, req); brige != nil {
|
||||
go app.socketReader(brige)
|
||||
}
|
||||
|
||||
default:
|
||||
filename := req.URL.Path[1:]
|
||||
if size := len(filename); size > 0 && filename[size-1] == '/' {
|
||||
filename = filename[:size-1]
|
||||
}
|
||||
|
||||
if !serveResourceFile(filename, w, req) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) socketReader(brige WebBrige) {
|
||||
var session Session
|
||||
events := make(chan DataObject, 1024)
|
||||
|
||||
for {
|
||||
message, ok := brige.ReadMessage()
|
||||
if !ok {
|
||||
events <- NewDataObject("disconnect")
|
||||
return
|
||||
}
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog(message)
|
||||
}
|
||||
|
||||
if obj := ParseDataText(message); obj != nil {
|
||||
command := obj.Tag()
|
||||
switch command {
|
||||
case "startSession":
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.WriteMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "reconnect":
|
||||
if sessionText, ok := obj.PropertyValue("session"); ok {
|
||||
if sessionID, err := strconv.Atoi(sessionText); err == nil {
|
||||
if session = app.sessions[sessionID]; session != nil {
|
||||
session.setBrige(events, brige)
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
session.writeInitScript(answer)
|
||||
if !brige.WriteMessage(answer.String()) {
|
||||
return
|
||||
}
|
||||
session.onReconnect()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
return
|
||||
}
|
||||
DebugLogF("Session #%d not exists", sessionID)
|
||||
} else {
|
||||
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`"session" key not found`)
|
||||
}
|
||||
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.WriteMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "answer":
|
||||
session.handleAnswer(obj)
|
||||
|
||||
case "imageLoaded":
|
||||
session.imageManager().imageLoaded(obj, session)
|
||||
|
||||
case "imageError":
|
||||
session.imageManager().imageLoadError(obj, session)
|
||||
|
||||
default:
|
||||
events <- obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sessionEventHandler(session Session, events chan DataObject, brige WebBrige) {
|
||||
for {
|
||||
data := <-events
|
||||
|
||||
switch command := data.Tag(); command {
|
||||
case "disconnect":
|
||||
session.onDisconnect()
|
||||
return
|
||||
|
||||
case "session-close":
|
||||
session.onFinish()
|
||||
session.App().removeSession(session.ID())
|
||||
brige.Close()
|
||||
|
||||
case "session-pause":
|
||||
session.onPause()
|
||||
|
||||
case "session-resume":
|
||||
session.onResume()
|
||||
|
||||
case "resize":
|
||||
session.handleResize(data)
|
||||
|
||||
default:
|
||||
session.handleViewEvent(command, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) startSession(params DataObject, events chan DataObject, brige WebBrige) (Session, string) {
|
||||
if app.createContentFunc == nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
session := newSession(app, app.nextSessionID(), "", params)
|
||||
session.setBrige(events, brige)
|
||||
if !session.setContent(app.createContentFunc(session), session) {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
app.sessions[session.ID()] = session
|
||||
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
answer.WriteString("sessionID = '")
|
||||
answer.WriteString(strconv.Itoa(session.ID()))
|
||||
answer.WriteString("';\n")
|
||||
session.writeInitScript(answer)
|
||||
answerText := answer.String()
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("Start session:")
|
||||
DebugLog(answerText)
|
||||
}
|
||||
return session, answerText
|
||||
}
|
||||
|
||||
// NewApplication - create the new application of the single view type.
|
||||
func NewApplication(name, icon string, createContentFunc func(Session) SessionContent) Application {
|
||||
app := new(application)
|
||||
app.init(name, icon)
|
||||
app.createContentFunc = createContentFunc
|
||||
return app
|
||||
}
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", url).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
|
||||
return err != nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package rui
|
||||
|
||||
type AudioPlayer interface {
|
||||
MediaPlayer
|
||||
}
|
||||
|
||||
type audioPlayerData struct {
|
||||
mediaPlayerData
|
||||
}
|
||||
|
||||
// NewAudioPlayer create new MediaPlayer object and return it
|
||||
func NewAudioPlayer(session Session, params Params) MediaPlayer {
|
||||
view := new(audioPlayerData)
|
||||
view.Init(session)
|
||||
view.tag = "AudioPlayer"
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newAudioPlayer(session Session) View {
|
||||
return NewAudioPlayer(session, nil)
|
||||
}
|
||||
|
||||
func (player *audioPlayerData) Init(session Session) {
|
||||
player.mediaPlayerData.Init(session)
|
||||
player.tag = "AudioPlayer"
|
||||
}
|
||||
|
||||
func (player *audioPlayerData) htmlTag() string {
|
||||
return "audio"
|
||||
}
|
|
@ -0,0 +1,716 @@
|
|||
package rui
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
// NoRepeat is value of the Repeat property of an background image:
|
||||
// The image is not repeated (and hence the background image painting area
|
||||
// will not necessarily be entirely covered). The position of the non-repeated
|
||||
// background image is defined by the background-position CSS property.
|
||||
NoRepeat = 0
|
||||
// RepeatXY is value of the Repeat property of an background image:
|
||||
// The image is repeated as much as needed to cover the whole background
|
||||
// image painting area. The last image will be clipped if it doesn't fit.
|
||||
RepeatXY = 1
|
||||
// RepeatX is value of the Repeat property of an background image:
|
||||
// The image is repeated horizontally as much as needed to cover
|
||||
// the whole width background image painting area. The image is not repeated vertically.
|
||||
// The last image will be clipped if it doesn't fit.
|
||||
RepeatX = 2
|
||||
// RepeatY is value of the Repeat property of an background image:
|
||||
// The image is repeated vertically as much as needed to cover
|
||||
// the whole height background image painting area. The image is not repeated horizontally.
|
||||
// The last image will be clipped if it doesn't fit.
|
||||
RepeatY = 3
|
||||
// RepeatRound is value of the Repeat property of an background image:
|
||||
// As the allowed space increases in size, the repeated images will stretch (leaving no gaps)
|
||||
// until there is room (space left >= half of the image width) for another one to be added.
|
||||
// When the next image is added, all of the current ones compress to allow room.
|
||||
RepeatRound = 4
|
||||
// RepeatSpace is value of the Repeat property of an background image:
|
||||
// The image is repeated as much as possible without clipping. The first and last images
|
||||
// are pinned to either side of the element, and whitespace is distributed evenly between the images.
|
||||
RepeatSpace = 5
|
||||
|
||||
// ScrollAttachment is value of the Attachment property of an background image:
|
||||
// The background is fixed relative to the element itself and does not scroll with its contents.
|
||||
// (It is effectively attached to the element's border.)
|
||||
ScrollAttachment = 0
|
||||
// FixedAttachment is value of the Attachment property of an background image:
|
||||
// The background is fixed relative to the viewport. Even if an element has
|
||||
// a scrolling mechanism, the background doesn't move with the element.
|
||||
FixedAttachment = 1
|
||||
// LocalAttachment is value of the Attachment property of an background image:
|
||||
// The background is fixed relative to the element's contents. If the element has a scrolling mechanism,
|
||||
// the background scrolls with the element's contents, and the background painting area
|
||||
// and background positioning area are relative to the scrollable area of the element
|
||||
// rather than to the border framing them.
|
||||
LocalAttachment = 2
|
||||
|
||||
// BorderBoxClip is value of the BackgroundClip property:
|
||||
// The background extends to the outside edge of the border (but underneath the border in z-ordering).
|
||||
BorderBoxClip = 0
|
||||
// PaddingBoxClip is value of the BackgroundClip property:
|
||||
// The background extends to the outside edge of the padding. No background is drawn beneath the border.
|
||||
PaddingBoxClip = 1
|
||||
// ContentBoxClip is value of the BackgroundClip property:
|
||||
// The background is painted within (clipped to) the content box.
|
||||
ContentBoxClip = 2
|
||||
|
||||
// ToTopGradient is value of the Direction property of a linear gradient. The value is equivalent to the 0deg angle
|
||||
ToTopGradient = 0
|
||||
// ToRightTopGradient is value of the Direction property of a linear gradient.
|
||||
ToRightTopGradient = 1
|
||||
// ToRightGradient is value of the Direction property of a linear gradient. The value is equivalent to the 90deg angle
|
||||
ToRightGradient = 2
|
||||
// ToRightBottomGradient is value of the Direction property of a linear gradient.
|
||||
ToRightBottomGradient = 3
|
||||
// ToBottomGradient is value of the Direction property of a linear gradient. The value is equivalent to the 180deg angle
|
||||
ToBottomGradient = 4
|
||||
// ToLeftBottomGradient is value of the Direction property of a linear gradient.
|
||||
ToLeftBottomGradient = 5
|
||||
// ToLeftGradient is value of the Direction property of a linear gradient. The value is equivalent to the 270deg angle
|
||||
ToLeftGradient = 6
|
||||
// ToLeftTopGradient is value of the Direction property of a linear gradient.
|
||||
ToLeftTopGradient = 7
|
||||
|
||||
// EllipseGradient is value of the Shape property of a radial gradient background:
|
||||
// the shape is an axis-aligned ellipse
|
||||
EllipseGradient = 0
|
||||
// CircleGradient is value of the Shape property of a radial gradient background:
|
||||
// the gradient's shape is a circle with constant radius
|
||||
CircleGradient = 1
|
||||
|
||||
// ClosestSideGradient is value of the Radius property of a radial gradient background:
|
||||
// The gradient's ending shape meets the side of the box closest to its center (for circles)
|
||||
// or meets both the vertical and horizontal sides closest to the center (for ellipses).
|
||||
ClosestSideGradient = 0
|
||||
// ClosestCornerGradient is value of the Radius property of a radial gradient background:
|
||||
// The gradient's ending shape is sized so that it exactly meets the closest corner
|
||||
// of the box from its center.
|
||||
ClosestCornerGradient = 1
|
||||
// FarthestSideGradient is value of the Radius property of a radial gradient background:
|
||||
// Similar to closest-side, except the ending shape is sized to meet the side of the box
|
||||
// farthest from its center (or vertical and horizontal sides).
|
||||
FarthestSideGradient = 2
|
||||
// FarthestCornerGradient is value of the Radius property of a radial gradient background:
|
||||
// The default value, the gradient's ending shape is sized so that it exactly meets
|
||||
// the farthest corner of the box from its center.
|
||||
FarthestCornerGradient = 3
|
||||
)
|
||||
|
||||
// BackgroundElement describes the background element.
|
||||
type BackgroundElement interface {
|
||||
Properties
|
||||
cssStyle(view View) string
|
||||
Tag() string
|
||||
}
|
||||
|
||||
type backgroundElement struct {
|
||||
propertyList
|
||||
}
|
||||
|
||||
type backgroundImage struct {
|
||||
backgroundElement
|
||||
}
|
||||
|
||||
// BackgroundGradientPoint define point on gradient straight line
|
||||
type BackgroundGradientPoint struct {
|
||||
// Pos - the distance from the start of the gradient straight line
|
||||
Pos SizeUnit
|
||||
// Color - the color of the point
|
||||
Color Color
|
||||
}
|
||||
|
||||
type backgroundGradient struct {
|
||||
backgroundElement
|
||||
}
|
||||
|
||||
type backgroundLinearGradient struct {
|
||||
backgroundGradient
|
||||
}
|
||||
|
||||
type backgroundRadialGradient struct {
|
||||
backgroundGradient
|
||||
}
|
||||
|
||||
// NewBackgroundImage creates the new background image
|
||||
func createBackground(obj DataObject) BackgroundElement {
|
||||
var result BackgroundElement = nil
|
||||
|
||||
switch obj.Tag() {
|
||||
case "image":
|
||||
image := new(backgroundImage)
|
||||
image.properties = map[string]interface{}{}
|
||||
result = image
|
||||
|
||||
case "linear-gradient":
|
||||
gradient := new(backgroundLinearGradient)
|
||||
gradient.properties = map[string]interface{}{}
|
||||
result = gradient
|
||||
|
||||
case "radial-gradient":
|
||||
gradient := new(backgroundRadialGradient)
|
||||
gradient.properties = map[string]interface{}{}
|
||||
result = gradient
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
count := obj.PropertyCount()
|
||||
for i := 0; i < count; i++ {
|
||||
if node := obj.Property(i); node.Type() == TextNode {
|
||||
if value := node.Text(); value != "" {
|
||||
result.Set(node.Tag(), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NewBackgroundImage creates the new background image
|
||||
func NewBackgroundImage(params Params) BackgroundElement {
|
||||
result := new(backgroundImage)
|
||||
result.properties = map[string]interface{}{}
|
||||
for tag, value := range params {
|
||||
result.Set(tag, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// NewBackgroundLinearGradient creates the new background linear gradient
|
||||
func NewBackgroundLinearGradient(params Params) BackgroundElement {
|
||||
result := new(backgroundLinearGradient)
|
||||
result.properties = map[string]interface{}{}
|
||||
for tag, value := range params {
|
||||
result.Set(tag, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// NewBackgroundRadialGradient creates the new background radial gradient
|
||||
func NewBackgroundRadialGradient(params Params) BackgroundElement {
|
||||
result := new(backgroundRadialGradient)
|
||||
result.properties = map[string]interface{}{}
|
||||
for tag, value := range params {
|
||||
result.Set(tag, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (image *backgroundImage) Tag() string {
|
||||
return "image"
|
||||
}
|
||||
|
||||
func (image *backgroundImage) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case "source":
|
||||
tag = Source
|
||||
|
||||
case Fit:
|
||||
tag = backgroundFit
|
||||
|
||||
case HorizontalAlign:
|
||||
tag = ImageHorizontalAlign
|
||||
|
||||
case VerticalAlign:
|
||||
tag = ImageVerticalAlign
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (image *backgroundImage) Set(tag string, value interface{}) bool {
|
||||
tag = image.normalizeTag(tag)
|
||||
switch tag {
|
||||
case Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign,
|
||||
backgroundFit, Source:
|
||||
return image.backgroundElement.Set(tag, value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (image *backgroundImage) Get(tag string) interface{} {
|
||||
return image.backgroundElement.Get(image.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (image *backgroundImage) cssStyle(view View) string {
|
||||
session := view.Session()
|
||||
if src, ok := stringProperty(image, Source, session); ok && src != "" {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
buffer.WriteString(`url(`)
|
||||
buffer.WriteString(src)
|
||||
buffer.WriteRune(')')
|
||||
|
||||
attachment, _ := enumProperty(image, Attachment, session, NoRepeat)
|
||||
values := enumProperties[Attachment].values
|
||||
if attachment > 0 && attachment < len(values) {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(values[attachment])
|
||||
}
|
||||
|
||||
align, _ := enumProperty(image, ImageHorizontalAlign, session, LeftAlign)
|
||||
values = enumProperties[ImageHorizontalAlign].values
|
||||
if align >= 0 && align < len(values) {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(values[align])
|
||||
} else {
|
||||
buffer.WriteString(` left`)
|
||||
}
|
||||
|
||||
align, _ = enumProperty(image, ImageVerticalAlign, session, TopAlign)
|
||||
values = enumProperties[ImageVerticalAlign].values
|
||||
if align >= 0 && align < len(values) {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(values[align])
|
||||
} else {
|
||||
buffer.WriteString(` top`)
|
||||
}
|
||||
|
||||
fit, _ := enumProperty(image, backgroundFit, session, NoneFit)
|
||||
values = enumProperties[backgroundFit].values
|
||||
if fit > 0 && fit < len(values) {
|
||||
|
||||
buffer.WriteString(` / `)
|
||||
buffer.WriteString(values[fit])
|
||||
|
||||
} else {
|
||||
|
||||
width, _ := sizeProperty(image, Width, session)
|
||||
height, _ := sizeProperty(image, Height, session)
|
||||
|
||||
if width.Type != Auto || height.Type != Auto {
|
||||
buffer.WriteString(` / `)
|
||||
buffer.WriteString(width.cssString("auto"))
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(height.cssString("auto"))
|
||||
}
|
||||
}
|
||||
|
||||
repeat, _ := enumProperty(image, Repeat, session, NoRepeat)
|
||||
values = enumProperties[Repeat].values
|
||||
if repeat >= 0 && repeat < len(values) {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(values[repeat])
|
||||
} else {
|
||||
buffer.WriteString(` no-repeat`)
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (gradient *backgroundGradient) Set(tag string, value interface{}) bool {
|
||||
|
||||
switch tag = strings.ToLower(tag); tag {
|
||||
case Repeat:
|
||||
return gradient.setBoolProperty(tag, value)
|
||||
|
||||
case Gradient:
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if value != "" {
|
||||
elements := strings.Split(value, `,`)
|
||||
if count := len(elements); count > 1 {
|
||||
points := make([]interface{}, count)
|
||||
for i, element := range elements {
|
||||
if strings.Contains(element, "@") {
|
||||
points[i] = element
|
||||
} else {
|
||||
var point BackgroundGradientPoint
|
||||
if point.setValue(element) {
|
||||
points[i] = point
|
||||
} else {
|
||||
ErrorLogF("Invalid gradient element #%d: %s", i, element)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
gradient.properties[Gradient] = points
|
||||
return true
|
||||
}
|
||||
|
||||
text := strings.Trim(value, " \n\r\t")
|
||||
if text[0] == '@' {
|
||||
gradient.properties[Gradient] = text
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
case []BackgroundGradientPoint:
|
||||
if len(value) >= 2 {
|
||||
gradient.properties[Gradient] = value
|
||||
return true
|
||||
}
|
||||
|
||||
case []Color:
|
||||
count := len(value)
|
||||
if count >= 2 {
|
||||
points := make([]BackgroundGradientPoint, count)
|
||||
for i, color := range value {
|
||||
points[i].Color = color
|
||||
points[i].Pos = AutoSize()
|
||||
}
|
||||
gradient.properties[Gradient] = points
|
||||
return true
|
||||
}
|
||||
|
||||
case []GradientPoint:
|
||||
count := len(value)
|
||||
if count >= 2 {
|
||||
points := make([]BackgroundGradientPoint, count)
|
||||
for i, point := range value {
|
||||
points[i].Color = point.Color
|
||||
points[i].Pos = Percent(point.Offset * 100)
|
||||
}
|
||||
gradient.properties[Gradient] = points
|
||||
return true
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
if count := len(value); count > 1 {
|
||||
points := make([]interface{}, count)
|
||||
for i, element := range value {
|
||||
switch element := element.(type) {
|
||||
case string:
|
||||
if strings.Contains(element, "@") {
|
||||
points[i] = element
|
||||
} else {
|
||||
var point BackgroundGradientPoint
|
||||
if !point.setValue(element) {
|
||||
ErrorLogF("Invalid gradient element #%d: %s", i, element)
|
||||
return false
|
||||
}
|
||||
points[i] = point
|
||||
}
|
||||
|
||||
case BackgroundGradientPoint:
|
||||
points[i] = element
|
||||
|
||||
case GradientPoint:
|
||||
points[i] = BackgroundGradientPoint{Color: element.Color, Pos: Percent(element.Offset * 100)}
|
||||
|
||||
case Color:
|
||||
points[i] = BackgroundGradientPoint{Color: element, Pos: AutoSize()}
|
||||
|
||||
default:
|
||||
ErrorLogF("Invalid gradient element #%d: %v", i, element)
|
||||
return false
|
||||
}
|
||||
}
|
||||
gradient.properties[Gradient] = points
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
ErrorLogF("Invalid gradient %v", value)
|
||||
return false
|
||||
}
|
||||
|
||||
return gradient.backgroundElement.Set(tag, value)
|
||||
}
|
||||
|
||||
func (point *BackgroundGradientPoint) setValue(value string) bool {
|
||||
var ok bool
|
||||
|
||||
switch elements := strings.Split(value, `:`); len(elements) {
|
||||
case 2:
|
||||
if point.Color, ok = StringToColor(elements[0]); !ok {
|
||||
return false
|
||||
}
|
||||
if point.Pos, ok = StringToSizeUnit(elements[1]); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
case 1:
|
||||
if point.Color, ok = StringToColor(elements[0]); !ok {
|
||||
return false
|
||||
}
|
||||
point.Pos = AutoSize()
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (gradient *backgroundGradient) writeGradient(view View, buffer *strings.Builder) bool {
|
||||
|
||||
value, ok := gradient.properties[Gradient]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
points := []BackgroundGradientPoint{}
|
||||
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if text, ok := view.Session().resolveConstants(value); ok && text != "" {
|
||||
elements := strings.Split(text, `,`)
|
||||
points := make([]BackgroundGradientPoint, len(elements))
|
||||
for i, element := range elements {
|
||||
if !points[i].setValue(element) {
|
||||
ErrorLogF(`Invalid gradient point #%d: "%s"`, i, element)
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`Invalid gradient: ` + value)
|
||||
return false
|
||||
}
|
||||
|
||||
case []BackgroundGradientPoint:
|
||||
points = value
|
||||
|
||||
case []interface{}:
|
||||
points = make([]BackgroundGradientPoint, len(value))
|
||||
for i, element := range value {
|
||||
switch element := element.(type) {
|
||||
case string:
|
||||
if text, ok := view.Session().resolveConstants(element); ok && text != "" {
|
||||
if !points[i].setValue(text) {
|
||||
ErrorLogF(`Invalid gradient point #%d: "%s"`, i, text)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
ErrorLogF(`Invalid gradient point #%d: "%s"`, i, text)
|
||||
return false
|
||||
}
|
||||
|
||||
case BackgroundGradientPoint:
|
||||
points[i] = element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(points) > 0 {
|
||||
for i, point := range points {
|
||||
if i > 0 {
|
||||
buffer.WriteString(`, `)
|
||||
}
|
||||
|
||||
buffer.WriteString(point.Color.cssString())
|
||||
if point.Pos.Type != Auto {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(point.Pos.cssString(""))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (gradient *backgroundLinearGradient) Tag() string {
|
||||
return "linear-gradient"
|
||||
}
|
||||
|
||||
func (gradient *backgroundLinearGradient) Set(tag string, value interface{}) bool {
|
||||
if tag == Direction {
|
||||
switch value := value.(type) {
|
||||
case AngleUnit:
|
||||
gradient.properties[Direction] = value
|
||||
return true
|
||||
|
||||
case string:
|
||||
var angle AngleUnit
|
||||
if ok, _ := angle.setValue(value); ok {
|
||||
gradient.properties[Direction] = angle
|
||||
return true
|
||||
}
|
||||
}
|
||||
return gradient.setEnumProperty(tag, value, enumProperties[Direction].values)
|
||||
}
|
||||
|
||||
return gradient.backgroundGradient.Set(tag, value)
|
||||
}
|
||||
|
||||
func (gradient *backgroundLinearGradient) cssStyle(view View) string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
session := view.Session()
|
||||
if repeating, _ := boolProperty(gradient, Repeating, session); repeating {
|
||||
buffer.WriteString(`repeating-linear-gradient(`)
|
||||
} else {
|
||||
buffer.WriteString(`linear-gradient(`)
|
||||
}
|
||||
|
||||
if value, ok := gradient.properties[Direction]; ok {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if text, ok := session.resolveConstants(value); ok {
|
||||
direction := enumProperties[Direction]
|
||||
if n, ok := enumStringToInt(text, direction.values, false); ok {
|
||||
buffer.WriteString(direction.cssValues[n])
|
||||
buffer.WriteString(", ")
|
||||
} else {
|
||||
if angle, ok := StringToAngleUnit(text); ok {
|
||||
buffer.WriteString(angle.cssString())
|
||||
buffer.WriteString(", ")
|
||||
} else {
|
||||
ErrorLog(`Invalid linear gradient direction: ` + text)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`Invalid linear gradient direction: ` + value)
|
||||
}
|
||||
|
||||
case int:
|
||||
values := enumProperties[Direction].cssValues
|
||||
if value >= 0 && value < len(values) {
|
||||
buffer.WriteString(values[value])
|
||||
buffer.WriteString(", ")
|
||||
} else {
|
||||
ErrorLogF(`Invalid linear gradient direction: %d`, value)
|
||||
}
|
||||
|
||||
case AngleUnit:
|
||||
buffer.WriteString(value.cssString())
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
}
|
||||
|
||||
if !gradient.writeGradient(view, buffer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
buffer.WriteString(") ")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (gradient *backgroundRadialGradient) Tag() string {
|
||||
return "radial-gradient"
|
||||
}
|
||||
|
||||
func (gradient *backgroundRadialGradient) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case Radius:
|
||||
tag = RadialGradientRadius
|
||||
|
||||
case Shape:
|
||||
tag = RadialGradientShape
|
||||
|
||||
case "x-center":
|
||||
tag = CenterX
|
||||
|
||||
case "y-center":
|
||||
tag = CenterY
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (gradient *backgroundRadialGradient) Set(tag string, value interface{}) bool {
|
||||
tag = gradient.normalizeTag(tag)
|
||||
switch tag {
|
||||
case RadialGradientRadius:
|
||||
switch value := value.(type) {
|
||||
case string, SizeUnit:
|
||||
return gradient.propertyList.Set(RadialGradientRadius, value)
|
||||
|
||||
case int:
|
||||
n := value
|
||||
if n >= 0 && n < len(enumProperties[RadialGradientRadius].values) {
|
||||
return gradient.propertyList.Set(RadialGradientRadius, value)
|
||||
}
|
||||
}
|
||||
ErrorLogF(`Invalid value of "%s" property: %v`, tag, value)
|
||||
|
||||
case RadialGradientShape:
|
||||
return gradient.propertyList.Set(RadialGradientShape, value)
|
||||
|
||||
case CenterX, CenterY:
|
||||
return gradient.propertyList.Set(tag, value)
|
||||
}
|
||||
|
||||
return gradient.backgroundGradient.Set(tag, value)
|
||||
}
|
||||
|
||||
func (gradient *backgroundRadialGradient) Get(tag string) interface{} {
|
||||
return gradient.backgroundGradient.Get(gradient.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (gradient *backgroundRadialGradient) cssStyle(view View) string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
session := view.Session()
|
||||
if repeating, _ := boolProperty(gradient, Repeating, session); repeating {
|
||||
buffer.WriteString(`repeating-radial-gradient(`)
|
||||
} else {
|
||||
buffer.WriteString(`radial-gradient(`)
|
||||
}
|
||||
|
||||
if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient {
|
||||
buffer.WriteString(`circle `)
|
||||
} else {
|
||||
buffer.WriteString(`ellipse `)
|
||||
}
|
||||
|
||||
if value, ok := gradient.properties[RadialGradientRadius]; ok {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if text, ok := session.resolveConstants(value); ok {
|
||||
values := enumProperties[RadialGradientRadius]
|
||||
if n, ok := enumStringToInt(text, values.values, false); ok {
|
||||
buffer.WriteString(values.cssValues[n])
|
||||
buffer.WriteString(" ")
|
||||
} else {
|
||||
if r, ok := StringToSizeUnit(text); ok && r.Type != Auto {
|
||||
buffer.WriteString(r.cssString(""))
|
||||
buffer.WriteString(" ")
|
||||
} else {
|
||||
ErrorLog(`Invalid linear gradient radius: ` + text)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`Invalid linear gradient radius: ` + value)
|
||||
}
|
||||
|
||||
case int:
|
||||
values := enumProperties[RadialGradientRadius].cssValues
|
||||
if value >= 0 && value < len(values) {
|
||||
buffer.WriteString(values[value])
|
||||
buffer.WriteString(" ")
|
||||
} else {
|
||||
ErrorLogF(`Invalid linear gradient radius: %d`, value)
|
||||
}
|
||||
|
||||
case SizeUnit:
|
||||
if value.Type != Auto {
|
||||
buffer.WriteString(value.cssString(""))
|
||||
buffer.WriteString(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x, _ := sizeProperty(gradient, CenterX, session)
|
||||
y, _ := sizeProperty(gradient, CenterX, session)
|
||||
if x.Type != Auto || y.Type != Auto {
|
||||
buffer.WriteString("at ")
|
||||
buffer.WriteString(x.cssString("50%"))
|
||||
buffer.WriteString(" ")
|
||||
buffer.WriteString(y.cssString("50%"))
|
||||
}
|
||||
|
||||
buffer.WriteString(", ")
|
||||
if !gradient.writeGradient(view, buffer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
buffer.WriteString(") ")
|
||||
|
||||
return buffer.String()
|
||||
}
|
|
@ -0,0 +1,710 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// NoneLine constant specifies that there is no border
|
||||
NoneLine = 0
|
||||
// SolidLine constant specifies the border/line as a solid line
|
||||
SolidLine = 1
|
||||
// DashedLine constant specifies the border/line as a dashed line
|
||||
DashedLine = 2
|
||||
// DottedLine constant specifies the border/line as a dotted line
|
||||
DottedLine = 3
|
||||
// DoubleLine constant specifies the border/line as a double solid line
|
||||
DoubleLine = 4
|
||||
// DoubleLine constant specifies the border/line as a double solid line
|
||||
WavyLine = 5
|
||||
|
||||
// LeftStyle is the constant for "left-style" property tag
|
||||
LeftStyle = "left-style"
|
||||
// RightStyle is the constant for "-right-style" property tag
|
||||
RightStyle = "right-style"
|
||||
// TopStyle is the constant for "top-style" property tag
|
||||
TopStyle = "top-style"
|
||||
// BottomStyle is the constant for "bottom-style" property tag
|
||||
BottomStyle = "bottom-style"
|
||||
// LeftWidth is the constant for "left-width" property tag
|
||||
LeftWidth = "left-width"
|
||||
// RightWidth is the constant for "-right-width" property tag
|
||||
RightWidth = "right-width"
|
||||
// TopWidth is the constant for "top-width" property tag
|
||||
TopWidth = "top-width"
|
||||
// BottomWidth is the constant for "bottom-width" property tag
|
||||
BottomWidth = "bottom-width"
|
||||
// LeftColor is the constant for "left-color" property tag
|
||||
LeftColor = "left-color"
|
||||
// RightColor is the constant for "-right-color" property tag
|
||||
RightColor = "right-color"
|
||||
// TopColor is the constant for "top-color" property tag
|
||||
TopColor = "top-color"
|
||||
// BottomColor is the constant for "bottom-color" property tag
|
||||
BottomColor = "bottom-color"
|
||||
)
|
||||
|
||||
// BorderProperty is the interface of a view border data
|
||||
type BorderProperty interface {
|
||||
Properties
|
||||
ruiStringer
|
||||
fmt.Stringer
|
||||
ViewBorders(session Session) ViewBorders
|
||||
delete(tag string)
|
||||
cssStyle(builder cssBuilder, session Session)
|
||||
cssWidth(builder cssBuilder, session Session)
|
||||
cssColor(builder cssBuilder, session Session)
|
||||
cssStyleValue(session Session) string
|
||||
cssWidthValue(session Session) string
|
||||
cssColorValue(session Session) string
|
||||
}
|
||||
|
||||
type borderProperty struct {
|
||||
propertyList
|
||||
}
|
||||
|
||||
func newBorderProperty(value interface{}) BorderProperty {
|
||||
border := new(borderProperty)
|
||||
border.properties = map[string]interface{}{}
|
||||
|
||||
if value != nil {
|
||||
switch value := value.(type) {
|
||||
case BorderProperty:
|
||||
return value
|
||||
|
||||
case DataObject:
|
||||
_ = border.setBorderObject(value)
|
||||
|
||||
case ViewBorder:
|
||||
border.properties[Style] = value.Style
|
||||
border.properties[Width] = value.Width
|
||||
border.properties[ColorProperty] = value.Color
|
||||
|
||||
case ViewBorders:
|
||||
if value.Left.Style == value.Right.Style &&
|
||||
value.Left.Style == value.Top.Style &&
|
||||
value.Left.Style == value.Bottom.Style {
|
||||
border.properties[Style] = value.Left.Style
|
||||
} else {
|
||||
border.properties[LeftStyle] = value.Left.Style
|
||||
border.properties[RightStyle] = value.Right.Style
|
||||
border.properties[TopStyle] = value.Top.Style
|
||||
border.properties[BottomStyle] = value.Bottom.Style
|
||||
}
|
||||
if value.Left.Width.Equal(value.Right.Width) &&
|
||||
value.Left.Width.Equal(value.Top.Width) &&
|
||||
value.Left.Width.Equal(value.Bottom.Width) {
|
||||
border.properties[Width] = value.Left.Width
|
||||
} else {
|
||||
border.properties[LeftWidth] = value.Left.Width
|
||||
border.properties[RightWidth] = value.Right.Width
|
||||
border.properties[TopWidth] = value.Top.Width
|
||||
border.properties[BottomWidth] = value.Bottom.Width
|
||||
}
|
||||
if value.Left.Color == value.Right.Color &&
|
||||
value.Left.Color == value.Top.Color &&
|
||||
value.Left.Color == value.Bottom.Color {
|
||||
border.properties[ColorProperty] = value.Left.Color
|
||||
} else {
|
||||
border.properties[LeftColor] = value.Left.Color
|
||||
border.properties[RightColor] = value.Right.Color
|
||||
border.properties[TopColor] = value.Top.Color
|
||||
border.properties[BottomColor] = value.Bottom.Color
|
||||
}
|
||||
|
||||
default:
|
||||
invalidPropertyValue(Border, value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return border
|
||||
}
|
||||
|
||||
// NewBorder creates the new BorderProperty
|
||||
func NewBorder(params Params) BorderProperty {
|
||||
border := new(borderProperty)
|
||||
border.properties = map[string]interface{}{}
|
||||
if params != nil {
|
||||
for _, tag := range []string{Style, Width, ColorProperty, Left, Right, Top, Bottom,
|
||||
LeftStyle, RightStyle, TopStyle, BottomStyle,
|
||||
LeftWidth, RightWidth, TopWidth, BottomWidth,
|
||||
LeftColor, RightColor, TopColor, BottomColor} {
|
||||
if value, ok := params[tag]; ok && value != nil {
|
||||
border.Set(tag, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return border
|
||||
}
|
||||
|
||||
func (border *borderProperty) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case BorderLeft, CellBorderLeft:
|
||||
return Left
|
||||
|
||||
case BorderRight, CellBorderRight:
|
||||
return Right
|
||||
|
||||
case BorderTop, CellBorderTop:
|
||||
return Top
|
||||
|
||||
case BorderBottom, CellBorderBottom:
|
||||
return Bottom
|
||||
|
||||
case BorderStyle, CellBorderStyle:
|
||||
return Style
|
||||
|
||||
case BorderLeftStyle, CellBorderLeftStyle, "style-left":
|
||||
return LeftStyle
|
||||
|
||||
case BorderRightStyle, CellBorderRightStyle, "style-right":
|
||||
return RightStyle
|
||||
|
||||
case BorderTopStyle, CellBorderTopStyle, "style-top":
|
||||
return TopStyle
|
||||
|
||||
case BorderBottomStyle, CellBorderBottomStyle, "style-bottom":
|
||||
return BottomStyle
|
||||
|
||||
case BorderWidth, CellBorderWidth:
|
||||
return Width
|
||||
|
||||
case BorderLeftWidth, CellBorderLeftWidth, "width-left":
|
||||
return LeftWidth
|
||||
|
||||
case BorderRightWidth, CellBorderRightWidth, "width-right":
|
||||
return RightWidth
|
||||
|
||||
case BorderTopWidth, CellBorderTopWidth, "width-top":
|
||||
return TopWidth
|
||||
|
||||
case BorderBottomWidth, CellBorderBottomWidth, "width-bottom":
|
||||
return BottomWidth
|
||||
|
||||
case BorderColor, CellBorderColor:
|
||||
return ColorProperty
|
||||
|
||||
case BorderLeftColor, CellBorderLeftColor, "color-left":
|
||||
return LeftColor
|
||||
|
||||
case BorderRightColor, CellBorderRightColor, "color-right":
|
||||
return RightColor
|
||||
|
||||
case BorderTopColor, CellBorderTopColor, "color-top":
|
||||
return TopColor
|
||||
|
||||
case BorderBottomColor, CellBorderBottomColor, "color-bottom":
|
||||
return BottomColor
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (border *borderProperty) ruiString(writer ruiWriter) {
|
||||
writer.startObject("_")
|
||||
|
||||
for _, tag := range []string{Style, Width, ColorProperty} {
|
||||
if value, ok := border.properties[tag]; ok {
|
||||
writer.writeProperty(Style, value)
|
||||
}
|
||||
}
|
||||
|
||||
for _, side := range []string{Top, Right, Bottom, Left} {
|
||||
style, okStyle := border.properties[side+"-"+Style]
|
||||
width, okWidth := border.properties[side+"-"+Width]
|
||||
color, okColor := border.properties[side+"-"+ColorProperty]
|
||||
if okStyle || okWidth || okColor {
|
||||
writer.startObjectProperty(side, "_")
|
||||
if okStyle {
|
||||
writer.writeProperty(Style, style)
|
||||
}
|
||||
if okWidth {
|
||||
writer.writeProperty(Width, width)
|
||||
}
|
||||
if okColor {
|
||||
writer.writeProperty(ColorProperty, color)
|
||||
}
|
||||
writer.endObject()
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
func (border *borderProperty) String() string {
|
||||
writer := newRUIWriter()
|
||||
border.ruiString(writer)
|
||||
return writer.finish()
|
||||
}
|
||||
|
||||
func (border *borderProperty) setSingleBorderObject(prefix string, obj DataObject) bool {
|
||||
result := true
|
||||
if text, ok := obj.PropertyValue(Style); ok {
|
||||
if !border.setEnumProperty(prefix+"-style", text, enumProperties[BorderStyle].values) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
if text, ok := obj.PropertyValue(ColorProperty); ok {
|
||||
if !border.setColorProperty(prefix+"-color", text) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
if text, ok := obj.PropertyValue("width"); ok {
|
||||
if !border.setSizeProperty(prefix+"-width", text) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (border *borderProperty) setBorderObject(obj DataObject) bool {
|
||||
result := true
|
||||
|
||||
for _, side := range []string{Top, Right, Bottom, Left} {
|
||||
if node := obj.PropertyWithTag(side); node != nil {
|
||||
if node.Type() == ObjectNode {
|
||||
if !border.setSingleBorderObject(side, node.Object()) {
|
||||
result = false
|
||||
}
|
||||
} else {
|
||||
notCompatibleType(side, node)
|
||||
result = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if text, ok := obj.PropertyValue(Style); ok {
|
||||
values := split4Values(text)
|
||||
styles := enumProperties[BorderStyle].values
|
||||
switch len(values) {
|
||||
case 1:
|
||||
if !border.setEnumProperty(Style, values[0], styles) {
|
||||
result = false
|
||||
}
|
||||
|
||||
case 4:
|
||||
for n, tag := range [4]string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||
if !border.setEnumProperty(tag, values[n], styles) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(Style, text)
|
||||
result = false
|
||||
}
|
||||
}
|
||||
|
||||
if text, ok := obj.PropertyValue(ColorProperty); ok {
|
||||
values := split4Values(text)
|
||||
switch len(values) {
|
||||
case 1:
|
||||
if !border.setColorProperty(ColorProperty, values[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
case 4:
|
||||
for n, tag := range [4]string{TopColor, RightColor, BottomColor, LeftColor} {
|
||||
if !border.setColorProperty(tag, values[n]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(ColorProperty, text)
|
||||
result = false
|
||||
}
|
||||
}
|
||||
|
||||
if text, ok := obj.PropertyValue(Width); ok {
|
||||
values := split4Values(text)
|
||||
switch len(values) {
|
||||
case 1:
|
||||
if !border.setSizeProperty(Width, values[0]) {
|
||||
result = false
|
||||
}
|
||||
|
||||
case 4:
|
||||
for n, tag := range [4]string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||
if !border.setSizeProperty(tag, values[n]) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(Width, text)
|
||||
result = false
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (border *borderProperty) Remove(tag string) {
|
||||
tag = border.normalizeTag(tag)
|
||||
|
||||
switch tag {
|
||||
case Style:
|
||||
for _, t := range []string{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||
delete(border.properties, t)
|
||||
}
|
||||
|
||||
case Width:
|
||||
for _, t := range []string{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||
delete(border.properties, t)
|
||||
}
|
||||
|
||||
case ColorProperty:
|
||||
for _, t := range []string{tag, TopColor, RightColor, BottomColor, LeftColor} {
|
||||
delete(border.properties, t)
|
||||
}
|
||||
|
||||
case Left, Right, Top, Bottom:
|
||||
border.Remove(tag + "-style")
|
||||
border.Remove(tag + "-width")
|
||||
border.Remove(tag + "-color")
|
||||
|
||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||
delete(border.properties, tag)
|
||||
if style, ok := border.properties[Style]; ok && style != nil {
|
||||
for _, t := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||
if t != tag {
|
||||
if _, ok := border.properties[t]; !ok {
|
||||
border.properties[t] = style
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||
delete(border.properties, tag)
|
||||
if width, ok := border.properties[Width]; ok && width != nil {
|
||||
for _, t := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||
if t != tag {
|
||||
if _, ok := border.properties[t]; !ok {
|
||||
border.properties[t] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case LeftColor, RightColor, TopColor, BottomColor:
|
||||
delete(border.properties, tag)
|
||||
if color, ok := border.properties[ColorProperty]; ok && color != nil {
|
||||
for _, t := range []string{TopColor, RightColor, BottomColor, LeftColor} {
|
||||
if t != tag {
|
||||
if _, ok := border.properties[t]; !ok {
|
||||
border.properties[t] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (border *borderProperty) Set(tag string, value interface{}) bool {
|
||||
if value == nil {
|
||||
border.Remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
tag = border.normalizeTag(tag)
|
||||
|
||||
switch tag {
|
||||
case Style:
|
||||
if border.setEnumProperty(Style, value, enumProperties[BorderStyle].values) {
|
||||
for _, side := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||
delete(border.properties, side)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case Width:
|
||||
if border.setSizeProperty(Width, value) {
|
||||
for _, side := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||
delete(border.properties, side)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case ColorProperty:
|
||||
if border.setColorProperty(ColorProperty, value) {
|
||||
for _, side := range []string{TopColor, RightColor, BottomColor, LeftColor} {
|
||||
delete(border.properties, side)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||
return border.setEnumProperty(tag, value, enumProperties[BorderStyle].values)
|
||||
|
||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||
return border.setSizeProperty(tag, value)
|
||||
|
||||
case LeftColor, RightColor, TopColor, BottomColor:
|
||||
return border.setColorProperty(tag, value)
|
||||
|
||||
case Left, Right, Top, Bottom:
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if obj := ParseDataText(value); obj != nil {
|
||||
return border.setSingleBorderObject(tag, obj)
|
||||
}
|
||||
|
||||
case DataObject:
|
||||
return border.setSingleBorderObject(tag, value)
|
||||
|
||||
case BorderProperty:
|
||||
styleTag := tag + "-" + Style
|
||||
if style := value.Get(styleTag); value != nil {
|
||||
border.properties[styleTag] = style
|
||||
}
|
||||
colorTag := tag + "-" + ColorProperty
|
||||
if color := value.Get(colorTag); value != nil {
|
||||
border.properties[colorTag] = color
|
||||
}
|
||||
widthTag := tag + "-" + Width
|
||||
if width := value.Get(widthTag); value != nil {
|
||||
border.properties[widthTag] = width
|
||||
}
|
||||
return true
|
||||
|
||||
case ViewBorder:
|
||||
border.properties[tag+"-"+Style] = value.Style
|
||||
border.properties[tag+"-"+Width] = value.Width
|
||||
border.properties[tag+"-"+ColorProperty] = value.Color
|
||||
return true
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (border *borderProperty) Get(tag string) interface{} {
|
||||
tag = border.normalizeTag(tag)
|
||||
|
||||
if result, ok := border.properties[tag]; ok {
|
||||
return result
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case Left, Right, Top, Bottom:
|
||||
result := newBorderProperty(nil)
|
||||
if style, ok := border.properties[tag+"-"+Style]; ok {
|
||||
result.Set(Style, style)
|
||||
} else if style, ok := border.properties[Style]; ok {
|
||||
result.Set(Style, style)
|
||||
}
|
||||
if width, ok := border.properties[tag+"-"+Width]; ok {
|
||||
result.Set(Width, width)
|
||||
} else if width, ok := border.properties[Width]; ok {
|
||||
result.Set(Width, width)
|
||||
}
|
||||
if color, ok := border.properties[tag+"-"+ColorProperty]; ok {
|
||||
result.Set(ColorProperty, color)
|
||||
} else if color, ok := border.properties[ColorProperty]; ok {
|
||||
result.Set(ColorProperty, color)
|
||||
}
|
||||
return result
|
||||
|
||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||
if style, ok := border.properties[tag]; ok {
|
||||
return style
|
||||
}
|
||||
return border.properties[Style]
|
||||
|
||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||
if width, ok := border.properties[tag]; ok {
|
||||
return width
|
||||
}
|
||||
return border.properties[Width]
|
||||
|
||||
case LeftColor, RightColor, TopColor, BottomColor:
|
||||
if color, ok := border.properties[tag]; ok {
|
||||
return color
|
||||
}
|
||||
return border.properties[ColorProperty]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (border *borderProperty) delete(tag string) {
|
||||
tag = border.normalizeTag(tag)
|
||||
remove := []string{}
|
||||
|
||||
switch tag {
|
||||
case Style:
|
||||
remove = []string{Style, LeftStyle, RightStyle, TopStyle, BottomStyle}
|
||||
|
||||
case Width:
|
||||
remove = []string{Width, LeftWidth, RightWidth, TopWidth, BottomWidth}
|
||||
|
||||
case ColorProperty:
|
||||
remove = []string{ColorProperty, LeftColor, RightColor, TopColor, BottomColor}
|
||||
|
||||
case Left, Right, Top, Bottom:
|
||||
if border.Get(Style) != nil {
|
||||
border.properties[tag+"-"+Style] = 0
|
||||
remove = []string{tag + "-" + ColorProperty, tag + "-" + Width}
|
||||
} else {
|
||||
remove = []string{tag + "-" + Style, tag + "-" + ColorProperty, tag + "-" + Width}
|
||||
}
|
||||
|
||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||
if border.Get(Style) != nil {
|
||||
border.properties[tag] = 0
|
||||
} else {
|
||||
remove = []string{tag}
|
||||
}
|
||||
|
||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||
if border.Get(Width) != nil {
|
||||
border.properties[tag] = AutoSize()
|
||||
} else {
|
||||
remove = []string{tag}
|
||||
}
|
||||
|
||||
case LeftColor, RightColor, TopColor, BottomColor:
|
||||
if border.Get(ColorProperty) != nil {
|
||||
border.properties[tag] = 0
|
||||
} else {
|
||||
remove = []string{tag}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range remove {
|
||||
delete(border.properties, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (border *borderProperty) ViewBorders(session Session) ViewBorders {
|
||||
|
||||
defStyle, _ := valueToEnum(border.getRaw(Style), BorderStyle, session, NoneLine)
|
||||
defWidth, _ := sizeProperty(border, Width, session)
|
||||
defColor, _ := colorProperty(border, ColorProperty, session)
|
||||
|
||||
getBorder := func(prefix string) ViewBorder {
|
||||
var result ViewBorder
|
||||
var ok bool
|
||||
if result.Style, ok = valueToEnum(border.getRaw(prefix+Style), BorderStyle, session, NoneLine); !ok {
|
||||
result.Style = defStyle
|
||||
}
|
||||
if result.Width, ok = sizeProperty(border, prefix+Width, session); !ok {
|
||||
result.Width = defWidth
|
||||
}
|
||||
if result.Color, ok = colorProperty(border, prefix+ColorProperty, session); !ok {
|
||||
result.Color = defColor
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return ViewBorders{
|
||||
Top: getBorder("top-"),
|
||||
Left: getBorder("left-"),
|
||||
Right: getBorder("right-"),
|
||||
Bottom: getBorder("bottom-"),
|
||||
}
|
||||
}
|
||||
|
||||
func (border *borderProperty) cssStyle(builder cssBuilder, session Session) {
|
||||
borders := border.ViewBorders(session)
|
||||
values := enumProperties[BorderStyle].cssValues
|
||||
if borders.Top.Style == borders.Right.Style &&
|
||||
borders.Top.Style == borders.Left.Style &&
|
||||
borders.Top.Style == borders.Bottom.Style {
|
||||
builder.add(BorderStyle, values[borders.Top.Style])
|
||||
} else {
|
||||
builder.addValues(BorderStyle, " ", values[borders.Top.Style],
|
||||
values[borders.Right.Style], values[borders.Bottom.Style], values[borders.Left.Style])
|
||||
}
|
||||
}
|
||||
|
||||
func (border *borderProperty) cssWidth(builder cssBuilder, session Session) {
|
||||
borders := border.ViewBorders(session)
|
||||
if borders.Top.Width == borders.Right.Width &&
|
||||
borders.Top.Width == borders.Left.Width &&
|
||||
borders.Top.Width == borders.Bottom.Width {
|
||||
if borders.Top.Width.Type != Auto {
|
||||
builder.add("border-width", borders.Top.Width.cssString("0"))
|
||||
}
|
||||
} else {
|
||||
builder.addValues("border-width", " ", borders.Top.Width.cssString("0"),
|
||||
borders.Right.Width.cssString("0"), borders.Bottom.Width.cssString("0"), borders.Left.Width.cssString("0"))
|
||||
}
|
||||
}
|
||||
|
||||
func (border *borderProperty) cssColor(builder cssBuilder, session Session) {
|
||||
borders := border.ViewBorders(session)
|
||||
if borders.Top.Color == borders.Right.Color &&
|
||||
borders.Top.Color == borders.Left.Color &&
|
||||
borders.Top.Color == borders.Bottom.Color {
|
||||
if borders.Top.Color != 0 {
|
||||
builder.add("border-color", borders.Top.Color.cssString())
|
||||
}
|
||||
} else {
|
||||
builder.addValues("border-color", " ", borders.Top.Color.cssString(),
|
||||
borders.Right.Color.cssString(), borders.Bottom.Color.cssString(), borders.Left.Color.cssString())
|
||||
}
|
||||
}
|
||||
|
||||
func (border *borderProperty) cssStyleValue(session Session) string {
|
||||
var builder cssValueBuilder
|
||||
border.cssStyle(&builder, session)
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
func (border *borderProperty) cssWidthValue(session Session) string {
|
||||
var builder cssValueBuilder
|
||||
border.cssWidth(&builder, session)
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
func (border *borderProperty) cssColorValue(session Session) string {
|
||||
var builder cssValueBuilder
|
||||
border.cssColor(&builder, session)
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
// ViewBorder describes parameters of a view border
|
||||
type ViewBorder struct {
|
||||
Style int
|
||||
Color Color
|
||||
Width SizeUnit
|
||||
}
|
||||
|
||||
// ViewBorders describes the top, right, bottom, and left border of a view
|
||||
type ViewBorders struct {
|
||||
Top, Right, Bottom, Left ViewBorder
|
||||
}
|
||||
|
||||
// AllTheSame returns true if all borders are the same
|
||||
func (border *ViewBorders) AllTheSame() bool {
|
||||
return border.Top.Style == border.Right.Style &&
|
||||
border.Top.Style == border.Left.Style &&
|
||||
border.Top.Style == border.Bottom.Style &&
|
||||
border.Top.Color == border.Right.Color &&
|
||||
border.Top.Color == border.Left.Color &&
|
||||
border.Top.Color == border.Bottom.Color &&
|
||||
border.Top.Width.Equal(border.Right.Width) &&
|
||||
border.Top.Width.Equal(border.Left.Width) &&
|
||||
border.Top.Width.Equal(border.Bottom.Width)
|
||||
}
|
||||
|
||||
func getBorder(style Properties, tag string) BorderProperty {
|
||||
if value := style.Get(tag); value != nil {
|
||||
if border, ok := value.(BorderProperty); ok {
|
||||
return border
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BorderProperty is the interface of a bounds property data
|
||||
type BoundsProperty interface {
|
||||
Properties
|
||||
ruiStringer
|
||||
fmt.Stringer
|
||||
Bounds(session Session) Bounds
|
||||
}
|
||||
|
||||
type boundsPropertyData struct {
|
||||
propertyList
|
||||
}
|
||||
|
||||
// NewBoundsProperty creates the new BoundsProperty object
|
||||
func NewBoundsProperty(params Params) BoundsProperty {
|
||||
bounds := new(boundsPropertyData)
|
||||
bounds.properties = map[string]interface{}{}
|
||||
if params != nil {
|
||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
||||
if value, ok := params[tag]; ok {
|
||||
bounds.Set(tag, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return bounds
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case MarginTop, PaddingTop, CellPaddingTop,
|
||||
"top-margin", "top-padding", "top-cell-padding":
|
||||
tag = Top
|
||||
|
||||
case MarginRight, PaddingRight, CellPaddingRight,
|
||||
"right-margin", "right-padding", "right-cell-padding":
|
||||
tag = Right
|
||||
|
||||
case MarginBottom, PaddingBottom, CellPaddingBottom,
|
||||
"bottom-margin", "bottom-padding", "bottom-cell-padding":
|
||||
tag = Bottom
|
||||
|
||||
case MarginLeft, PaddingLeft, CellPaddingLeft,
|
||||
"left-margin", "left-padding", "left-cell-padding":
|
||||
tag = Left
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) ruiString(writer ruiWriter) {
|
||||
writer.startObject("_")
|
||||
|
||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
||||
if value, ok := bounds.properties[tag]; ok {
|
||||
writer.writeProperty(Style, value)
|
||||
}
|
||||
}
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) String() string {
|
||||
writer := newRUIWriter()
|
||||
bounds.ruiString(writer)
|
||||
return writer.finish()
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) Remove(tag string) {
|
||||
bounds.propertyList.Remove(bounds.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) Set(tag string, value interface{}) bool {
|
||||
if value == nil {
|
||||
bounds.Remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
tag = bounds.normalizeTag(tag)
|
||||
|
||||
switch tag {
|
||||
case Top, Right, Bottom, Left:
|
||||
return bounds.setSizeProperty(tag, value)
|
||||
|
||||
default:
|
||||
ErrorLogF(`"%s" property is not compatible with the BoundsProperty`, tag)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) Get(tag string) interface{} {
|
||||
tag = bounds.normalizeTag(tag)
|
||||
if value, ok := bounds.properties[tag]; ok {
|
||||
return value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bounds *boundsPropertyData) Bounds(session Session) Bounds {
|
||||
top, _ := sizeProperty(bounds, Top, session)
|
||||
right, _ := sizeProperty(bounds, Right, session)
|
||||
bottom, _ := sizeProperty(bounds, Bottom, session)
|
||||
left, _ := sizeProperty(bounds, Left, session)
|
||||
return Bounds{Top: top, Right: right, Bottom: bottom, Left: left}
|
||||
}
|
||||
|
||||
// Bounds describe bounds of rectangle.
|
||||
type Bounds struct {
|
||||
Top, Right, Bottom, Left SizeUnit
|
||||
}
|
||||
|
||||
// DefaultBounds return bounds with Top, Right, Bottom and Left fields set to Auto
|
||||
func DefaultBounds() Bounds {
|
||||
return Bounds{
|
||||
Top: SizeUnit{Type: Auto, Value: 0},
|
||||
Right: SizeUnit{Type: Auto, Value: 0},
|
||||
Bottom: SizeUnit{Type: Auto, Value: 0},
|
||||
Left: SizeUnit{Type: Auto, Value: 0},
|
||||
}
|
||||
}
|
||||
|
||||
// SetAll set the Top, Right, Bottom and Left field to the equal value
|
||||
func (bounds *Bounds) SetAll(value SizeUnit) {
|
||||
bounds.Top = value
|
||||
bounds.Right = value
|
||||
bounds.Bottom = value
|
||||
bounds.Left = value
|
||||
}
|
||||
|
||||
func (bounds *Bounds) parse(value string, session Session) bool {
|
||||
var ok bool
|
||||
if value, ok = session.resolveConstants(value); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
values := strings.Split(value, ",")
|
||||
switch len(values) {
|
||||
case 1:
|
||||
if bounds.Left, ok = StringToSizeUnit(values[0]); !ok {
|
||||
return false
|
||||
}
|
||||
bounds.Right.Type = bounds.Left.Type
|
||||
bounds.Right.Value = bounds.Left.Value
|
||||
bounds.Top.Type = bounds.Left.Type
|
||||
bounds.Top.Value = bounds.Left.Value
|
||||
bounds.Bottom.Type = bounds.Left.Type
|
||||
bounds.Bottom.Value = bounds.Left.Value
|
||||
return true
|
||||
|
||||
case 5:
|
||||
if values[4] != "" {
|
||||
ErrorLog("invalid Bounds value '" + value + "' (needs 1 or 4 elements separeted by comma)")
|
||||
return false
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 4:
|
||||
if bounds.Top, ok = StringToSizeUnit(values[0]); ok {
|
||||
if bounds.Right, ok = StringToSizeUnit(values[1]); ok {
|
||||
if bounds.Bottom, ok = StringToSizeUnit(values[2]); ok {
|
||||
if bounds.Left, ok = StringToSizeUnit(values[3]); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ErrorLog("invalid Bounds value '" + value + "' (needs 1 or 4 elements separeted by comma)")
|
||||
return false
|
||||
}
|
||||
|
||||
func (bounds *Bounds) setFromProperties(tag, topTag, rightTag, bottomTag, leftTag string, properties Properties, session Session) {
|
||||
bounds.Top = AutoSize()
|
||||
if size, ok := sizeProperty(properties, tag, session); ok {
|
||||
bounds.Top = size
|
||||
}
|
||||
bounds.Right = bounds.Top
|
||||
bounds.Bottom = bounds.Top
|
||||
bounds.Left = bounds.Top
|
||||
|
||||
if size, ok := sizeProperty(properties, topTag, session); ok {
|
||||
bounds.Top = size
|
||||
}
|
||||
if size, ok := sizeProperty(properties, rightTag, session); ok {
|
||||
bounds.Right = size
|
||||
}
|
||||
if size, ok := sizeProperty(properties, bottomTag, session); ok {
|
||||
bounds.Bottom = size
|
||||
}
|
||||
if size, ok := sizeProperty(properties, leftTag, session); ok {
|
||||
bounds.Left = size
|
||||
}
|
||||
}
|
||||
|
||||
func (bounds *Bounds) allFieldsAuto() bool {
|
||||
return bounds.Left.Type == Auto &&
|
||||
bounds.Top.Type == Auto &&
|
||||
bounds.Right.Type == Auto &&
|
||||
bounds.Bottom.Type == Auto
|
||||
}
|
||||
|
||||
/*
|
||||
func (bounds *Bounds) allFieldsZero() bool {
|
||||
return (bounds.Left.Type == Auto || bounds.Left.Value == 0) &&
|
||||
(bounds.Top.Type == Auto || bounds.Top.Value == 0) &&
|
||||
(bounds.Right.Type == Auto || bounds.Right.Value == 0) &&
|
||||
(bounds.Bottom.Type == Auto || bounds.Bottom.Value == 0)
|
||||
}
|
||||
*/
|
||||
|
||||
func (bounds *Bounds) allFieldsEqual() bool {
|
||||
if bounds.Left.Type == bounds.Top.Type &&
|
||||
bounds.Left.Type == bounds.Right.Type &&
|
||||
bounds.Left.Type == bounds.Bottom.Type {
|
||||
return bounds.Left.Type == Auto ||
|
||||
(bounds.Left.Value == bounds.Top.Value &&
|
||||
bounds.Left.Value == bounds.Right.Value &&
|
||||
bounds.Left.Value == bounds.Bottom.Value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (bounds Bounds) writeCSSString(buffer *strings.Builder, textForAuto string) {
|
||||
buffer.WriteString(bounds.Top.cssString(textForAuto))
|
||||
if !bounds.allFieldsEqual() {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(bounds.Right.cssString(textForAuto))
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(bounds.Bottom.cssString(textForAuto))
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(bounds.Left.cssString(textForAuto))
|
||||
}
|
||||
}
|
||||
|
||||
// String convert Bounds to string
|
||||
func (bounds *Bounds) String() string {
|
||||
if bounds.allFieldsEqual() {
|
||||
return bounds.Top.String()
|
||||
}
|
||||
return bounds.Top.String() + "," + bounds.Right.String() + "," +
|
||||
bounds.Bottom.String() + "," + bounds.Left.String()
|
||||
}
|
||||
|
||||
func (bounds *Bounds) cssValue(tag string, builder cssBuilder) {
|
||||
if bounds.allFieldsEqual() {
|
||||
builder.add(tag, bounds.Top.cssString("0"))
|
||||
} else {
|
||||
builder.addValues(tag, " ", bounds.Top.cssString("0"), bounds.Right.cssString("0"),
|
||||
bounds.Bottom.cssString("0"), bounds.Left.cssString("0"))
|
||||
}
|
||||
}
|
||||
|
||||
func (bounds *Bounds) cssString() string {
|
||||
var builder cssValueBuilder
|
||||
bounds.cssValue("", &builder)
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
func (properties *propertyList) setBounds(tag string, value interface{}) bool {
|
||||
if !properties.setSimpleProperty(tag, value) {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if strings.Contains(value, ",") {
|
||||
values := split4Values(value)
|
||||
count := len(values)
|
||||
switch count {
|
||||
case 1:
|
||||
value = values[0]
|
||||
|
||||
case 4:
|
||||
bounds := NewBoundsProperty(nil)
|
||||
for i, tag := range []string{Top, Right, Bottom, Left} {
|
||||
if !bounds.Set(tag, values[i]) {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
properties.properties[tag] = bounds
|
||||
return true
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return properties.setSizeProperty(tag, value)
|
||||
|
||||
case SizeUnit:
|
||||
properties.properties[tag] = value
|
||||
|
||||
case Bounds:
|
||||
properties.properties[tag] = value
|
||||
|
||||
case BoundsProperty:
|
||||
properties.properties[tag] = value
|
||||
|
||||
case DataObject:
|
||||
bounds := NewBoundsProperty(nil)
|
||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
||||
if text, ok := value.PropertyValue(tag); ok {
|
||||
if !bounds.Set(tag, text) {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
properties.properties[tag] = bounds
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (properties *propertyList) boundsProperty(tag string) BoundsProperty {
|
||||
if value, ok := properties.properties[tag]; ok {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
bounds := NewBoundsProperty(nil)
|
||||
for _, t := range []string{Top, Right, Bottom, Left} {
|
||||
bounds.Set(t, value)
|
||||
}
|
||||
return bounds
|
||||
|
||||
case SizeUnit:
|
||||
bounds := NewBoundsProperty(nil)
|
||||
for _, t := range []string{Top, Right, Bottom, Left} {
|
||||
bounds.Set(t, value)
|
||||
}
|
||||
return bounds
|
||||
|
||||
case BoundsProperty:
|
||||
return value
|
||||
|
||||
case Bounds:
|
||||
return NewBoundsProperty(Params{
|
||||
Top: value.Top,
|
||||
Right: value.Right,
|
||||
Bottom: value.Bottom,
|
||||
Left: value.Left})
|
||||
}
|
||||
}
|
||||
|
||||
return NewBoundsProperty(nil)
|
||||
}
|
||||
|
||||
func (properties *propertyList) removeBoundsSide(mainTag, sideTag string) {
|
||||
bounds := properties.boundsProperty(mainTag)
|
||||
if bounds.Get(sideTag) != nil {
|
||||
bounds.Remove(sideTag)
|
||||
properties.properties[mainTag] = bounds
|
||||
}
|
||||
}
|
||||
|
||||
func (properties *propertyList) setBoundsSide(mainTag, sideTag string, value interface{}) bool {
|
||||
bounds := properties.boundsProperty(mainTag)
|
||||
if bounds.Set(sideTag, value) {
|
||||
properties.properties[mainTag] = bounds
|
||||
return true
|
||||
}
|
||||
|
||||
notCompatibleType(sideTag, value)
|
||||
return false
|
||||
}
|
||||
|
||||
func boundsProperty(properties Properties, tag string, session Session) (Bounds, bool) {
|
||||
if value := properties.Get(tag); value != nil {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if text, ok := session.resolveConstants(value); ok {
|
||||
if size, ok := StringToSizeUnit(text); ok {
|
||||
return Bounds{Left: size, Top: size, Right: size, Bottom: size}, true
|
||||
}
|
||||
}
|
||||
|
||||
case SizeUnit:
|
||||
return Bounds{Left: value, Top: value, Right: value, Bottom: value}, true
|
||||
|
||||
case Bounds:
|
||||
return value, true
|
||||
|
||||
case BoundsProperty:
|
||||
return value.Bounds(session), true
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, value)
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultBounds(), false
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package rui
|
||||
|
||||
/*
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoundsSet(t *testing.T) {
|
||||
|
||||
session := createTestSession(t)
|
||||
|
||||
obj := NewDataObject("Test")
|
||||
obj.SetPropertyValue("x", "10")
|
||||
obj.SetPropertyValue("padding", "8px")
|
||||
obj.SetPropertyValue("margins", "16mm,10pt,12in,auto")
|
||||
obj.SetPropertyValue("fail1", "x16mm")
|
||||
obj.SetPropertyValue("fail2", "16mm,10pt,12in")
|
||||
obj.SetPropertyValue("fail3", "x16mm,10pt,12in,auto")
|
||||
obj.SetPropertyValue("fail4", "16mm,x10pt,12in,auto")
|
||||
obj.SetPropertyValue("fail5", "16mm,10pt,x12in,auto")
|
||||
obj.SetPropertyValue("fail6", "16mm,10pt,12in,autoo")
|
||||
|
||||
const failAttrsCount = 6
|
||||
|
||||
var bounds Bounds
|
||||
if bounds.setProperty(obj, "padding", session) {
|
||||
if bounds.Left.Type != SizeInPixel || bounds.Left.Value != 8 ||
|
||||
bounds.Left != bounds.Right ||
|
||||
bounds.Left != bounds.Top ||
|
||||
bounds.Left != bounds.Bottom {
|
||||
t.Errorf("set padding error, result %v", bounds)
|
||||
}
|
||||
}
|
||||
|
||||
if bounds.setProperty(obj, "margins", session) {
|
||||
if bounds.Top.Type != SizeInMM || bounds.Top.Value != 16 ||
|
||||
bounds.Right.Type != SizeInPt || bounds.Right.Value != 10 ||
|
||||
bounds.Bottom.Type != SizeInInch || bounds.Bottom.Value != 12 ||
|
||||
bounds.Left.Type != Auto {
|
||||
t.Errorf("set margins error, result %v", bounds)
|
||||
}
|
||||
}
|
||||
|
||||
ignoreTestLog = true
|
||||
for i := 1; i <= failAttrsCount; i++ {
|
||||
if bounds.setProperty(obj, "fail"+strconv.Itoa(i), session) {
|
||||
t.Errorf("set 'fail' error, result %v", bounds)
|
||||
}
|
||||
}
|
||||
ignoreTestLog = false
|
||||
|
||||
obj.SetPropertyValue("padding-left", "10mm")
|
||||
obj.SetPropertyValue("padding-top", "4pt")
|
||||
obj.SetPropertyValue("padding-right", "12in")
|
||||
obj.SetPropertyValue("padding-bottom", "8px")
|
||||
|
||||
if bounds.setProperty(obj, "padding", session) {
|
||||
if bounds.Left.Type != SizeInMM || bounds.Left.Value != 10 ||
|
||||
bounds.Top.Type != SizeInPt || bounds.Top.Value != 4 ||
|
||||
bounds.Right.Type != SizeInInch || bounds.Right.Value != 12 ||
|
||||
bounds.Bottom.Type != SizeInPixel || bounds.Bottom.Value != 8 {
|
||||
t.Errorf("set margins error, result %v", bounds)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range []string{"padding-left", "padding-top", "padding-right", "padding-bottom"} {
|
||||
if old, ok := obj.PropertyValue(tag); ok {
|
||||
ignoreTestLog = true
|
||||
obj.SetPropertyValue(tag, "x")
|
||||
if bounds.setProperty(obj, "padding", session) {
|
||||
t.Errorf("set \"%s\" value \"x\": result %v ", tag, bounds)
|
||||
}
|
||||
ignoreTestLog = false
|
||||
obj.SetPropertyValue(tag, old)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundsWriteData(t *testing.T) {
|
||||
|
||||
_ = createTestSession(t)
|
||||
|
||||
bounds := Bounds{
|
||||
SizeUnit{SizeInPixel, 8},
|
||||
SizeUnit{SizeInInch, 10},
|
||||
SizeUnit{SizeInPt, 12},
|
||||
SizeUnit{Auto, 0},
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
bounds.writeData(buffer)
|
||||
str := buffer.String()
|
||||
if str != `"8px,10in,12pt,auto"` {
|
||||
t.Errorf("result `%s`, expected `\"8px,10dip,12pt,auto\"`", str)
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,36 @@
|
|||
package rui
|
||||
|
||||
// Button - button view
|
||||
type Button interface {
|
||||
CustomView
|
||||
}
|
||||
|
||||
type buttonData struct {
|
||||
CustomViewData
|
||||
}
|
||||
|
||||
// NewButton create new Button object and return it
|
||||
func NewButton(session Session, params Params) Button {
|
||||
button := new(buttonData)
|
||||
InitCustomView(button, "Button", session, params)
|
||||
return button
|
||||
}
|
||||
|
||||
func newButton(session Session) View {
|
||||
return NewButton(session, nil)
|
||||
}
|
||||
|
||||
func (button *buttonData) CreateSuperView(session Session) View {
|
||||
return NewListLayout(session, Params{
|
||||
Semantics: ButtonSemantics,
|
||||
Style: "ruiButton",
|
||||
StyleDisabled: "ruiDisabledButton",
|
||||
HorizontalAlign: CenterAlign,
|
||||
VerticalAlign: CenterAlign,
|
||||
Orientation: StartToEndOrientation,
|
||||
})
|
||||
}
|
||||
|
||||
func (button *buttonData) Focusable() bool {
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package rui
|
||||
|
||||
import "strings"
|
||||
|
||||
// DrawFunction is the constant for the "draw-function" property tag.
|
||||
// The "draw-function" property sets the draw function of CanvasView.
|
||||
// The function should have the following format: func(Canvas)
|
||||
const DrawFunction = "draw-function"
|
||||
|
||||
// CanvasView interface of a custom draw view
|
||||
type CanvasView interface {
|
||||
View
|
||||
Redraw()
|
||||
}
|
||||
|
||||
type canvasViewData struct {
|
||||
viewData
|
||||
drawer func(Canvas)
|
||||
}
|
||||
|
||||
// NewCanvasView creates the new custom draw view
|
||||
func NewCanvasView(session Session, params Params) CanvasView {
|
||||
view := new(canvasViewData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newCanvasView(session Session) View {
|
||||
return NewCanvasView(session, nil)
|
||||
}
|
||||
|
||||
// Init initialize fields of ViewsContainer by default values
|
||||
func (canvasView *canvasViewData) Init(session Session) {
|
||||
canvasView.viewData.Init(session)
|
||||
canvasView.tag = "CanvasView"
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case "draw-func":
|
||||
tag = DrawFunction
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) Remove(tag string) {
|
||||
canvasView.remove(canvasView.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) remove(tag string) {
|
||||
if tag == DrawFunction {
|
||||
canvasView.drawer = nil
|
||||
canvasView.Redraw()
|
||||
} else {
|
||||
canvasView.viewData.remove(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) Set(tag string, value interface{}) bool {
|
||||
return canvasView.set(canvasView.normalizeTag(tag), value)
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) set(tag string, value interface{}) bool {
|
||||
if tag == DrawFunction {
|
||||
if value == nil {
|
||||
canvasView.drawer = nil
|
||||
} else if fn, ok := value.(func(Canvas)); ok {
|
||||
canvasView.drawer = fn
|
||||
} else {
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
canvasView.Redraw()
|
||||
return true
|
||||
}
|
||||
|
||||
return canvasView.viewData.set(tag, value)
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) Get(tag string) interface{} {
|
||||
return canvasView.get(canvasView.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) get(tag string) interface{} {
|
||||
if tag == DrawFunction {
|
||||
return canvasView.drawer
|
||||
}
|
||||
return canvasView.viewData.get(tag)
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) htmlTag() string {
|
||||
return "canvas"
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) Redraw() {
|
||||
if canvasView.drawer != nil {
|
||||
canvas := newCanvas(canvasView)
|
||||
canvas.ClearRect(0, 0, canvasView.frame.Width, canvasView.frame.Height)
|
||||
if canvasView.drawer != nil {
|
||||
canvasView.drawer(canvas)
|
||||
}
|
||||
canvasView.session.runScript(canvas.finishDraw())
|
||||
}
|
||||
}
|
||||
|
||||
func (canvasView *canvasViewData) onResize(self View, x, y, width, height float64) {
|
||||
canvasView.viewData.onResize(self, x, y, width, height)
|
||||
canvasView.Redraw()
|
||||
}
|
||||
|
||||
// RedrawCanvasView finds CanvasView with canvasViewID and redraws it
|
||||
func RedrawCanvasView(rootView View, canvasViewID string) {
|
||||
if canvas := CanvasViewByID(rootView, canvasViewID); canvas != nil {
|
||||
canvas.Redraw()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,374 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckboxChangedEvent is the constant for "checkbox-event" property tag.
|
||||
// The "checkbox-event" event occurs when the checkbox becomes checked/unchecked.
|
||||
// The main listener format: func(Checkbox, bool), where the second argument is the checkbox state.
|
||||
const CheckboxChangedEvent = "checkbox-event"
|
||||
|
||||
// Checkbox - checkbox view
|
||||
type Checkbox interface {
|
||||
ViewsContainer
|
||||
}
|
||||
|
||||
type checkboxData struct {
|
||||
viewsContainerData
|
||||
checkedListeners []func(Checkbox, bool)
|
||||
}
|
||||
|
||||
// NewCheckbox create new Checkbox object and return it
|
||||
func NewCheckbox(session Session, params Params) Checkbox {
|
||||
view := new(checkboxData)
|
||||
view.Init(session)
|
||||
setInitParams(view, Params{
|
||||
ClickEvent: checkboxClickListener,
|
||||
KeyDownEvent: checkboxKeyListener,
|
||||
})
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newCheckbox(session Session) View {
|
||||
return NewCheckbox(session, nil)
|
||||
}
|
||||
|
||||
func (button *checkboxData) Init(session Session) {
|
||||
button.viewsContainerData.Init(session)
|
||||
button.tag = "Checkbox"
|
||||
button.systemClass = "ruiGridLayout ruiCheckbox"
|
||||
button.checkedListeners = []func(Checkbox, bool){}
|
||||
}
|
||||
|
||||
func (button *checkboxData) Focusable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (button *checkboxData) Get(tag string) interface{} {
|
||||
switch strings.ToLower(tag) {
|
||||
case CheckboxChangedEvent:
|
||||
return button.checkedListeners
|
||||
}
|
||||
|
||||
return button.viewsContainerData.Get(tag)
|
||||
}
|
||||
|
||||
func (button *checkboxData) Set(tag string, value interface{}) bool {
|
||||
switch strings.ToLower(tag) {
|
||||
case CheckboxChangedEvent:
|
||||
ok := button.setChangedListener(value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
}
|
||||
return ok
|
||||
|
||||
case Checked:
|
||||
oldChecked := button.checked()
|
||||
if !button.setBoolProperty(Checked, value) {
|
||||
return false
|
||||
}
|
||||
if button.created {
|
||||
checked := button.checked()
|
||||
if checked != oldChecked {
|
||||
button.changedCheckboxState(checked)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
||||
if button.setEnumProperty(tag, value, enumProperties[tag].values) {
|
||||
if button.created {
|
||||
htmlID := button.htmlID()
|
||||
updateCSSStyle(htmlID, button.session)
|
||||
updateInnerHTML(htmlID, button.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case VerticalAlign:
|
||||
if button.setEnumProperty(tag, value, enumProperties[tag].values) {
|
||||
if button.created {
|
||||
updateCSSProperty(button.htmlID()+"content", "align-items", button.cssVerticalAlign(), button.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case HorizontalAlign:
|
||||
if button.setEnumProperty(tag, value, enumProperties[tag].values) {
|
||||
if button.created {
|
||||
updateCSSProperty(button.htmlID()+"content", "justify-items", button.cssHorizontalAlign(), button.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case CellVerticalAlign, CellHorizontalAlign, CellWidth, CellHeight:
|
||||
return false
|
||||
}
|
||||
|
||||
return button.viewsContainerData.Set(tag, value)
|
||||
}
|
||||
|
||||
func (button *checkboxData) Remove(tag string) {
|
||||
switch strings.ToLower(tag) {
|
||||
case CheckboxChangedEvent:
|
||||
if len(button.checkedListeners) > 0 {
|
||||
button.checkedListeners = []func(Checkbox, bool){}
|
||||
}
|
||||
|
||||
case Checked:
|
||||
oldChecked := button.checked()
|
||||
delete(button.properties, tag)
|
||||
if oldChecked {
|
||||
button.changedCheckboxState(false)
|
||||
}
|
||||
|
||||
case CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
||||
delete(button.properties, tag)
|
||||
htmlID := button.htmlID()
|
||||
updateCSSStyle(htmlID, button.session)
|
||||
updateInnerHTML(htmlID, button.session)
|
||||
|
||||
case VerticalAlign:
|
||||
delete(button.properties, tag)
|
||||
updateCSSProperty(button.htmlID()+"content", "align-items", button.cssVerticalAlign(), button.session)
|
||||
|
||||
case HorizontalAlign:
|
||||
delete(button.properties, tag)
|
||||
updateCSSProperty(button.htmlID()+"content", "justify-items", button.cssHorizontalAlign(), button.session)
|
||||
|
||||
default:
|
||||
button.viewsContainerData.Remove(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (button *checkboxData) checked() bool {
|
||||
checked, _ := boolProperty(button, Checked, button.Session())
|
||||
return checked
|
||||
}
|
||||
|
||||
func (button *checkboxData) changedCheckboxState(state bool) {
|
||||
for _, listener := range button.checkedListeners {
|
||||
listener(button, state)
|
||||
}
|
||||
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
button.htmlCheckbox(buffer, state)
|
||||
button.Session().runScript(fmt.Sprintf(`updateInnerHTML('%v', '%v');`, button.htmlID()+"checkbox", buffer.String()))
|
||||
}
|
||||
|
||||
func checkboxClickListener(view View) {
|
||||
view.Set(Checked, !IsCheckboxChecked(view, ""))
|
||||
}
|
||||
|
||||
func checkboxKeyListener(view View, event KeyEvent) {
|
||||
switch event.Code {
|
||||
case "Enter", "Space":
|
||||
view.Set(Checked, !IsCheckboxChecked(view, ""))
|
||||
}
|
||||
}
|
||||
|
||||
func (button *checkboxData) setChangedListener(value interface{}) bool {
|
||||
if value == nil {
|
||||
if len(button.checkedListeners) > 0 {
|
||||
button.checkedListeners = []func(Checkbox, bool){}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case func(Checkbox, bool):
|
||||
button.checkedListeners = []func(Checkbox, bool){value}
|
||||
|
||||
case func(bool):
|
||||
fn := func(view Checkbox, checked bool) {
|
||||
value(checked)
|
||||
}
|
||||
button.checkedListeners = []func(Checkbox, bool){fn}
|
||||
|
||||
case []func(Checkbox, bool):
|
||||
button.checkedListeners = value
|
||||
|
||||
case []func(bool):
|
||||
listeners := make([]func(Checkbox, bool), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
listeners[i] = func(view Checkbox, checked bool) {
|
||||
val(checked)
|
||||
}
|
||||
}
|
||||
button.checkedListeners = listeners
|
||||
|
||||
case []interface{}:
|
||||
listeners := make([]func(Checkbox, bool), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch val := val.(type) {
|
||||
case func(Checkbox, bool):
|
||||
listeners[i] = val
|
||||
|
||||
case func(bool):
|
||||
listeners[i] = func(view Checkbox, date bool) {
|
||||
val(date)
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
button.checkedListeners = listeners
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (button *checkboxData) cssStyle(self View, builder cssBuilder) {
|
||||
session := button.Session()
|
||||
vAlign, _ := enumStyledProperty(button, CheckboxVerticalAlign, LeftAlign)
|
||||
hAlign, _ := enumStyledProperty(button, CheckboxHorizontalAlign, TopAlign)
|
||||
switch hAlign {
|
||||
case CenterAlign:
|
||||
if vAlign == BottomAlign {
|
||||
builder.add("grid-template-rows", "1fr auto")
|
||||
} else {
|
||||
builder.add("grid-template-rows", "auto 1fr")
|
||||
}
|
||||
|
||||
case RightAlign:
|
||||
builder.add("grid-template-columns", "1fr auto")
|
||||
|
||||
default:
|
||||
builder.add("grid-template-columns", "auto 1fr")
|
||||
}
|
||||
|
||||
if gap, ok := sizeConstant(session, "ruiCheckboxGap"); ok && gap.Type != Auto && gap.Value > 0 {
|
||||
builder.add("gap", gap.cssString("0"))
|
||||
}
|
||||
|
||||
builder.add("align-items", "stretch")
|
||||
builder.add("justify-items", "stretch")
|
||||
|
||||
button.viewsContainerData.cssStyle(self, builder)
|
||||
}
|
||||
|
||||
func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool) (int, int) {
|
||||
vAlign, _ := enumStyledProperty(button, CheckboxVerticalAlign, LeftAlign)
|
||||
hAlign, _ := enumStyledProperty(button, CheckboxHorizontalAlign, TopAlign)
|
||||
|
||||
buffer.WriteString(`<div id="`)
|
||||
buffer.WriteString(button.htmlID())
|
||||
buffer.WriteString(`checkbox" style="display: grid;`)
|
||||
if hAlign == CenterAlign {
|
||||
buffer.WriteString(" justify-items: center; grid-column-start: 1; grid-column-end: 2;")
|
||||
if vAlign == BottomAlign {
|
||||
buffer.WriteString(" grid-row-start: 2; grid-row-end: 3;")
|
||||
} else {
|
||||
buffer.WriteString(" grid-row-start: 1; grid-row-end: 2;")
|
||||
}
|
||||
} else {
|
||||
if hAlign == RightAlign {
|
||||
buffer.WriteString(" grid-column-start: 2; grid-column-end: 3;")
|
||||
} else {
|
||||
buffer.WriteString(" grid-column-start: 1; grid-column-end: 2;")
|
||||
}
|
||||
buffer.WriteString(" grid-row-start: 1; grid-row-end: 2;")
|
||||
switch vAlign {
|
||||
case BottomAlign:
|
||||
buffer.WriteString(" align-items: end;")
|
||||
|
||||
case CenterAlign:
|
||||
buffer.WriteString(" align-items: center;")
|
||||
|
||||
default:
|
||||
buffer.WriteString(" align-items: start;")
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString(`">`)
|
||||
if checked {
|
||||
buffer.WriteString(button.Session().checkboxOnImage())
|
||||
} else {
|
||||
buffer.WriteString(button.Session().checkboxOffImage())
|
||||
}
|
||||
buffer.WriteString(`</div>`)
|
||||
|
||||
return vAlign, hAlign
|
||||
}
|
||||
|
||||
func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
|
||||
vCheckboxAlign, hCheckboxAlign := button.htmlCheckbox(buffer, IsCheckboxChecked(button, ""))
|
||||
|
||||
buffer.WriteString(`<div id="`)
|
||||
buffer.WriteString(button.htmlID())
|
||||
buffer.WriteString(`content" style="display: grid;`)
|
||||
if hCheckboxAlign == LeftAlign {
|
||||
buffer.WriteString(" grid-column-start: 2; grid-column-end: 3;")
|
||||
} else {
|
||||
buffer.WriteString(" grid-column-start: 1; grid-column-end: 2;")
|
||||
}
|
||||
|
||||
if hCheckboxAlign == CenterAlign && vCheckboxAlign != BottomAlign {
|
||||
buffer.WriteString(" grid-row-start: 2; grid-row-end: 3;")
|
||||
} else {
|
||||
buffer.WriteString(" grid-row-start: 1; grid-row-end: 2;")
|
||||
}
|
||||
|
||||
buffer.WriteString(" align-items: ")
|
||||
buffer.WriteString(button.cssVerticalAlign())
|
||||
buffer.WriteRune(';')
|
||||
|
||||
buffer.WriteString(" justify-items: ")
|
||||
buffer.WriteString(button.cssHorizontalAlign())
|
||||
buffer.WriteRune(';')
|
||||
|
||||
buffer.WriteString(`">`)
|
||||
button.viewsContainerData.htmlSubviews(self, buffer)
|
||||
buffer.WriteString(`</div>`)
|
||||
}
|
||||
|
||||
func (button *checkboxData) cssHorizontalAlign() string {
|
||||
align, _ := enumStyledProperty(button, HorizontalAlign, TopAlign)
|
||||
values := enumProperties[CellHorizontalAlign].cssValues
|
||||
if align >= 0 && align < len(values) {
|
||||
return values[align]
|
||||
}
|
||||
return values[0]
|
||||
}
|
||||
|
||||
func (button *checkboxData) cssVerticalAlign() string {
|
||||
align, _ := enumStyledProperty(button, VerticalAlign, TopAlign)
|
||||
values := enumProperties[CellVerticalAlign].cssValues
|
||||
if align >= 0 && align < len(values) {
|
||||
return values[align]
|
||||
}
|
||||
return values[0]
|
||||
}
|
||||
|
||||
// IsCheckboxChecked returns true if the Checkbox is checked, false otherwise.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func IsCheckboxChecked(view View, subviewID string) bool {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if checked := view.Get(Checked); checked != nil {
|
||||
if b, ok := checked.(bool); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Color - represent color in argb format
|
||||
type Color uint32
|
||||
|
||||
// ARGB - return alpha, red, green and blue components of the color
|
||||
func (color Color) ARGB() (uint8, uint8, uint8, uint8) {
|
||||
return uint8(color >> 24),
|
||||
uint8((color >> 16) & 0xFF),
|
||||
uint8((color >> 8) & 0xFF),
|
||||
uint8(color & 0xFF)
|
||||
}
|
||||
|
||||
// Alpha - return the alpha component of the color
|
||||
func (color Color) Alpha() int {
|
||||
return int((color >> 24) & 0xFF)
|
||||
}
|
||||
|
||||
// Red - return the red component of the color
|
||||
func (color Color) Red() int {
|
||||
return int((color >> 16) & 0xFF)
|
||||
}
|
||||
|
||||
// Green - return the green component of the color
|
||||
func (color Color) Green() int {
|
||||
return int((color >> 8) & 0xFF)
|
||||
}
|
||||
|
||||
// Blue - return the blue component of the color
|
||||
func (color Color) Blue() int {
|
||||
return int(color & 0xFF)
|
||||
}
|
||||
|
||||
// String get a text representation of the color
|
||||
func (color Color) String() string {
|
||||
return fmt.Sprintf("#%08X", int(color))
|
||||
}
|
||||
|
||||
func (color Color) rgbString() string {
|
||||
return fmt.Sprintf("#%06X", int(color&0xFFFFFF))
|
||||
}
|
||||
|
||||
// writeData write a text representation of the color to the buffer
|
||||
func (color Color) writeData(buffer *bytes.Buffer) {
|
||||
buffer.WriteString(color.String())
|
||||
}
|
||||
|
||||
// cssString get the text representation of the color in CSS format
|
||||
func (color Color) cssString() string {
|
||||
red := color.Red()
|
||||
green := color.Green()
|
||||
blue := color.Blue()
|
||||
|
||||
if alpha := color.Alpha(); alpha < 255 {
|
||||
aText := fmt.Sprintf("%.2f", float64(alpha)/255.0)
|
||||
if len(aText) > 1 {
|
||||
aText = aText[1:]
|
||||
}
|
||||
return fmt.Sprintf("rgba(%d,%d,%d,%s)", red, green, blue, aText)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("rgb(%d,%d,%d)", red, green, blue)
|
||||
}
|
||||
|
||||
// StringToColor converts the string argument to Color value
|
||||
func StringToColor(text string) (Color, bool) {
|
||||
|
||||
text = strings.Trim(text, " \t\r\n")
|
||||
if text == "" {
|
||||
ErrorLog(`Invalid color value: ""`)
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if text[0] == '#' {
|
||||
c, err := strconv.ParseUint(text[1:], 16, 32)
|
||||
if err != nil {
|
||||
ErrorLog("Set color value error: " + err.Error())
|
||||
return 0, false
|
||||
}
|
||||
|
||||
switch len(text) - 1 {
|
||||
case 8:
|
||||
return Color(c), true
|
||||
|
||||
case 6:
|
||||
return Color(c | 0xFF000000), true
|
||||
|
||||
case 4:
|
||||
a := (c >> 12) & 0xF
|
||||
r := (c >> 8) & 0xF
|
||||
g := (c >> 4) & 0xF
|
||||
b := c & 0xF
|
||||
return Color((a << 28) | (a << 24) | (r << 20) | (r << 16) | (g << 12) | (g << 8) | (b << 4) | b), true
|
||||
|
||||
case 3:
|
||||
r := (c >> 8) & 0xF
|
||||
g := (c >> 4) & 0xF
|
||||
b := c & 0xF
|
||||
return Color(0xFF000000 | (r << 20) | (r << 16) | (g << 12) | (g << 8) | (b << 4) | b), true
|
||||
}
|
||||
|
||||
ErrorLog(`Invalid color format: "` + text + `". Valid formats: #AARRGGBB, #RRGGBB, #ARGB, #RGB`)
|
||||
return 0, false
|
||||
}
|
||||
|
||||
parseRGB := func(args string) []int {
|
||||
args = strings.Trim(args, " \t")
|
||||
count := len(args)
|
||||
if count < 3 || args[0] != '(' || args[count-1] != ')' {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
arg := strings.Split(args[1:count-1], ",")
|
||||
result := make([]int, len(arg))
|
||||
for i, val := range arg {
|
||||
val = strings.Trim(val, " \t")
|
||||
size := len(val)
|
||||
if size == 0 {
|
||||
return []int{}
|
||||
}
|
||||
if val[size-1] == '%' {
|
||||
if n, err := strconv.Atoi(val[:size-1]); err == nil && n >= 0 && n <= 100 {
|
||||
result[i] = n * 255 / 100
|
||||
} else {
|
||||
return []int{}
|
||||
}
|
||||
} else if strings.ContainsRune(val, '.') {
|
||||
if val[0] == '.' {
|
||||
val = "0" + val
|
||||
}
|
||||
if f, err := strconv.ParseFloat(val, 32); err == nil && f >= 0 && f <= 1 {
|
||||
result[i] = int(f * 255)
|
||||
} else {
|
||||
return []int{}
|
||||
}
|
||||
} else {
|
||||
if n, err := strconv.Atoi(val); err == nil && n >= 0 && n <= 255 {
|
||||
result[i] = n
|
||||
} else {
|
||||
return []int{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
text = strings.ToLower(text)
|
||||
if strings.HasPrefix(text, "rgba") {
|
||||
args := parseRGB(text[4:])
|
||||
if len(args) == 4 {
|
||||
return Color((args[3] << 24) | (args[0] << 16) | (args[1] << 8) | args[2]), true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(text, "rgb") {
|
||||
args := parseRGB(text[3:])
|
||||
if len(args) == 3 {
|
||||
return Color(0xFF000000 | (args[0] << 16) | (args[1] << 8) | args[2]), true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO hsl(360,100%,50%), hsla(360,100%,50%,.5)
|
||||
|
||||
if color, ok := colorConstants[text]; ok {
|
||||
return color, true
|
||||
}
|
||||
|
||||
ErrorLog(`Invalid color format: "` + text + `"`)
|
||||
return 0, false
|
||||
}
|
|
@ -0,0 +1,448 @@
|
|||
package rui
|
||||
|
||||
const (
|
||||
// Black color constant
|
||||
Black Color = 0xff000000
|
||||
// Silver color constant
|
||||
Silver Color = 0xffc0c0c0
|
||||
// Gray color constant
|
||||
Gray Color = 0xff808080
|
||||
// White color constant
|
||||
White Color = 0xffffffff
|
||||
// Maroon color constant
|
||||
Maroon Color = 0xff800000
|
||||
// Red color constant
|
||||
Red Color = 0xffff0000
|
||||
// Purple color constant
|
||||
Purple Color = 0xff800080
|
||||
// Fuchsia color constant
|
||||
Fuchsia Color = 0xffff00ff
|
||||
// Green color constant
|
||||
Green Color = 0xff008000
|
||||
// Lime color constant
|
||||
Lime Color = 0xff00ff00
|
||||
// Olive color constant
|
||||
Olive Color = 0xff808000
|
||||
// Yellow color constant
|
||||
Yellow Color = 0xffffff00
|
||||
// Navy color constant
|
||||
Navy Color = 0xff000080
|
||||
// Blue color constant
|
||||
Blue Color = 0xff0000ff
|
||||
// Teal color constant
|
||||
Teal Color = 0xff008080
|
||||
// Aqua color constant
|
||||
Aqua Color = 0xff00ffff
|
||||
// Orange color constant
|
||||
Orange Color = 0xffffa500
|
||||
// AliceBlue color constant
|
||||
AliceBlue Color = 0xfff0f8ff
|
||||
// AntiqueWhite color constant
|
||||
AntiqueWhite Color = 0xfffaebd7
|
||||
// Aquamarine color constant
|
||||
Aquamarine Color = 0xff7fffd4
|
||||
// Azure color constant
|
||||
Azure Color = 0xfff0ffff
|
||||
// Beige color constant
|
||||
Beige Color = 0xfff5f5dc
|
||||
// Bisque color constant
|
||||
Bisque Color = 0xffffe4c4
|
||||
// BlanchedAlmond color constant
|
||||
BlanchedAlmond Color = 0xffffebcd
|
||||
// BlueViolet color constant
|
||||
BlueViolet Color = 0xff8a2be2
|
||||
// Brown color constant
|
||||
Brown Color = 0xffa52a2a
|
||||
// Burlywood color constant
|
||||
Burlywood Color = 0xffdeb887
|
||||
// CadetBlue color constant
|
||||
CadetBlue Color = 0xff5f9ea0
|
||||
// Chartreuse color constant
|
||||
Chartreuse Color = 0xff7fff00
|
||||
// Chocolate color constant
|
||||
Chocolate Color = 0xffd2691e
|
||||
// Coral color constant
|
||||
Coral Color = 0xffff7f50
|
||||
// CornflowerBlue color constant
|
||||
CornflowerBlue Color = 0xff6495ed
|
||||
// Cornsilk color constant
|
||||
Cornsilk Color = 0xfffff8dc
|
||||
// Crimson color constant
|
||||
Crimson Color = 0xffdc143c
|
||||
// Cyan color constant
|
||||
Cyan Color = 0xff00ffff
|
||||
// DarkBlue color constant
|
||||
DarkBlue Color = 0xff00008b
|
||||
// DarkCyan color constant
|
||||
DarkCyan Color = 0xff008b8b
|
||||
// DarkGoldenRod color constant
|
||||
DarkGoldenRod Color = 0xffb8860b
|
||||
// DarkGray color constant
|
||||
DarkGray Color = 0xffa9a9a9
|
||||
// DarkGreen color constant
|
||||
DarkGreen Color = 0xff006400
|
||||
// DarkGrey color constant
|
||||
DarkGrey Color = 0xffa9a9a9
|
||||
// DarkKhaki color constant
|
||||
DarkKhaki Color = 0xffbdb76b
|
||||
// DarkMagenta color constant
|
||||
DarkMagenta Color = 0xff8b008b
|
||||
// DarkOliveGreen color constant
|
||||
DarkOliveGreen Color = 0xff556b2f
|
||||
// DarkOrange color constant
|
||||
DarkOrange Color = 0xffff8c00
|
||||
// DarkOrchid color constant
|
||||
DarkOrchid Color = 0xff9932cc
|
||||
// DarkRed color constant
|
||||
DarkRed Color = 0xff8b0000
|
||||
// DarkSalmon color constant
|
||||
DarkSalmon Color = 0xffe9967a
|
||||
// DarkSeaGreen color constant
|
||||
DarkSeaGreen Color = 0xff8fbc8f
|
||||
// DarkSlateBlue color constant
|
||||
DarkSlateBlue Color = 0xff483d8b
|
||||
// DarkSlateGray color constant
|
||||
DarkSlateGray Color = 0xff2f4f4f
|
||||
// Darkslategrey color constant
|
||||
Darkslategrey Color = 0xff2f4f4f
|
||||
// DarkTurquoise color constant
|
||||
DarkTurquoise Color = 0xff00ced1
|
||||
// DarkViolet color constant
|
||||
DarkViolet Color = 0xff9400d3
|
||||
// DeepPink color constant
|
||||
DeepPink Color = 0xffff1493
|
||||
// DeepSkyBlue color constant
|
||||
DeepSkyBlue Color = 0xff00bfff
|
||||
// DimGray color constant
|
||||
DimGray Color = 0xff696969
|
||||
// DimGrey color constant
|
||||
DimGrey Color = 0xff696969
|
||||
// DodgerBlue color constant
|
||||
DodgerBlue Color = 0xff1e90ff
|
||||
// FireBrick color constant
|
||||
FireBrick Color = 0xffb22222
|
||||
// FloralWhite color constant
|
||||
FloralWhite Color = 0xfffffaf0
|
||||
// ForestGreen color constant
|
||||
ForestGreen Color = 0xff228b22
|
||||
// Gainsboro color constant
|
||||
Gainsboro Color = 0xffdcdcdc
|
||||
// GhostWhite color constant
|
||||
GhostWhite Color = 0xfff8f8ff
|
||||
// Gold color constant
|
||||
Gold Color = 0xffffd700
|
||||
// GoldenRod color constant
|
||||
GoldenRod Color = 0xffdaa520
|
||||
// GreenyEllow color constant
|
||||
GreenyEllow Color = 0xffadff2f
|
||||
// Grey color constant
|
||||
Grey Color = 0xff808080
|
||||
// Honeydew color constant
|
||||
Honeydew Color = 0xfff0fff0
|
||||
// HotPink color constant
|
||||
HotPink Color = 0xffff69b4
|
||||
// IndianRed color constant
|
||||
IndianRed Color = 0xffcd5c5c
|
||||
// Indigo color constant
|
||||
Indigo Color = 0xff4b0082
|
||||
// Ivory color constant
|
||||
Ivory Color = 0xfffffff0
|
||||
// Khaki color constant
|
||||
Khaki Color = 0xfff0e68c
|
||||
// Lavender color constant
|
||||
Lavender Color = 0xffe6e6fa
|
||||
// LavenderBlush color constant
|
||||
LavenderBlush Color = 0xfffff0f5
|
||||
// LawnGreen color constant
|
||||
LawnGreen Color = 0xff7cfc00
|
||||
// LemonChiffon color constant
|
||||
LemonChiffon Color = 0xfffffacd
|
||||
// LightBlue color constant
|
||||
LightBlue Color = 0xffadd8e6
|
||||
// LightCoral color constant
|
||||
LightCoral Color = 0xfff08080
|
||||
// LightCyan color constant
|
||||
LightCyan Color = 0xffe0ffff
|
||||
// LightGoldenrodYellow color constant
|
||||
LightGoldenrodYellow Color = 0xfffafad2
|
||||
// LightGray color constant
|
||||
LightGray Color = 0xffd3d3d3
|
||||
// LightGreen color constant
|
||||
LightGreen Color = 0xff90ee90
|
||||
// LightGrey color constant
|
||||
LightGrey Color = 0xffd3d3d3
|
||||
// LightPink color constant
|
||||
LightPink Color = 0xffffb6c1
|
||||
// LightSalmon color constant
|
||||
LightSalmon Color = 0xffffa07a
|
||||
// LightSeaGreen color constant
|
||||
LightSeaGreen Color = 0xff20b2aa
|
||||
// LightSkyBlue color constant
|
||||
LightSkyBlue Color = 0xff87cefa
|
||||
// LightSlateGray color constant
|
||||
LightSlateGray Color = 0xff778899
|
||||
// LightSlateGrey color constant
|
||||
LightSlateGrey Color = 0xff778899
|
||||
// LightSteelBlue color constant
|
||||
LightSteelBlue Color = 0xffb0c4de
|
||||
// LightYellow color constant
|
||||
LightYellow Color = 0xffffffe0
|
||||
// LimeGreen color constant
|
||||
LimeGreen Color = 0xff32cd32
|
||||
// Linen color constant
|
||||
Linen Color = 0xfffaf0e6
|
||||
// Magenta color constant
|
||||
Magenta Color = 0xffff00ff
|
||||
// MediumAquamarine color constant
|
||||
MediumAquamarine Color = 0xff66cdaa
|
||||
// MediumBlue color constant
|
||||
MediumBlue Color = 0xff0000cd
|
||||
// MediumOrchid color constant
|
||||
MediumOrchid Color = 0xffba55d3
|
||||
// MediumPurple color constant
|
||||
MediumPurple Color = 0xff9370db
|
||||
// MediumSeaGreen color constant
|
||||
MediumSeaGreen Color = 0xff3cb371
|
||||
// MediumSlateBlue color constant
|
||||
MediumSlateBlue Color = 0xff7b68ee
|
||||
// MediumSpringGreen color constant
|
||||
MediumSpringGreen Color = 0xff00fa9a
|
||||
// MediumTurquoise color constant
|
||||
MediumTurquoise Color = 0xff48d1cc
|
||||
// MediumVioletRed color constant
|
||||
MediumVioletRed Color = 0xffc71585
|
||||
// MidnightBlue color constant
|
||||
MidnightBlue Color = 0xff191970
|
||||
// MintCream color constant
|
||||
MintCream Color = 0xfff5fffa
|
||||
// MistyRose color constant
|
||||
MistyRose Color = 0xffffe4e1
|
||||
// Moccasin color constant
|
||||
Moccasin Color = 0xffffe4b5
|
||||
// NavajoWhite color constant
|
||||
NavajoWhite Color = 0xffffdead
|
||||
// OldLace color constant
|
||||
OldLace Color = 0xfffdf5e6
|
||||
// OliveDrab color constant
|
||||
OliveDrab Color = 0xff6b8e23
|
||||
// OrangeRed color constant
|
||||
OrangeRed Color = 0xffff4500
|
||||
// Orchid color constant
|
||||
Orchid Color = 0xffda70d6
|
||||
// PaleGoldenrod color constant
|
||||
PaleGoldenrod Color = 0xffeee8aa
|
||||
// PaleGreen color constant
|
||||
PaleGreen Color = 0xff98fb98
|
||||
// PaleTurquoise color constant
|
||||
PaleTurquoise Color = 0xffafeeee
|
||||
// PaleVioletRed color constant
|
||||
PaleVioletRed Color = 0xffdb7093
|
||||
// PapayaWhip color constant
|
||||
PapayaWhip Color = 0xffffefd5
|
||||
// PeachPuff color constant
|
||||
PeachPuff Color = 0xffffdab9
|
||||
// Peru color constant
|
||||
Peru Color = 0xffcd853f
|
||||
// Pink color constant
|
||||
Pink Color = 0xffffc0cb
|
||||
// Plum color constant
|
||||
Plum Color = 0xffdda0dd
|
||||
// PowderBlue color constant
|
||||
PowderBlue Color = 0xffb0e0e6
|
||||
// RosyBrown color constant
|
||||
RosyBrown Color = 0xffbc8f8f
|
||||
// RoyalBlue color constant
|
||||
RoyalBlue Color = 0xff4169e1
|
||||
// SaddleBrown color constant
|
||||
SaddleBrown Color = 0xff8b4513
|
||||
// Salmon color constant
|
||||
Salmon Color = 0xfffa8072
|
||||
// SandyBrown color constant
|
||||
SandyBrown Color = 0xfff4a460
|
||||
// SeaGreen color constant
|
||||
SeaGreen Color = 0xff2e8b57
|
||||
// SeaShell color constant
|
||||
SeaShell Color = 0xfffff5ee
|
||||
// Sienna color constant
|
||||
Sienna Color = 0xffa0522d
|
||||
// SkyBlue color constant
|
||||
SkyBlue Color = 0xff87ceeb
|
||||
// SlateBlue color constant
|
||||
SlateBlue Color = 0xff6a5acd
|
||||
// SlateGray color constant
|
||||
SlateGray Color = 0xff708090
|
||||
// SlateGrey color constant
|
||||
SlateGrey Color = 0xff708090
|
||||
// Snow color constant
|
||||
Snow Color = 0xfffffafa
|
||||
// SpringGreen color constant
|
||||
SpringGreen Color = 0xff00ff7f
|
||||
// SteelBlue color constant
|
||||
SteelBlue Color = 0xff4682b4
|
||||
// Tan color constant
|
||||
Tan Color = 0xffd2b48c
|
||||
// Thistle color constant
|
||||
Thistle Color = 0xffd8bfd8
|
||||
// Tomato color constant
|
||||
Tomato Color = 0xffff6347
|
||||
// Turquoise color constant
|
||||
Turquoise Color = 0xff40e0d0
|
||||
// Violet color constant
|
||||
Violet Color = 0xffee82ee
|
||||
// Wheat color constant
|
||||
Wheat Color = 0xfff5deb3
|
||||
// Whitesmoke color constant
|
||||
Whitesmoke Color = 0xfff5f5f5
|
||||
// YellowGreen color constant
|
||||
YellowGreen Color = 0xff9acd32
|
||||
)
|
||||
|
||||
var colorConstants = map[string]Color{
|
||||
"black": 0xff000000,
|
||||
"silver": 0xffc0c0c0,
|
||||
"gray": 0xff808080,
|
||||
"white": 0xffffffff,
|
||||
"maroon": 0xff800000,
|
||||
"red": 0xffff0000,
|
||||
"purple": 0xff800080,
|
||||
"fuchsia": 0xffff00ff,
|
||||
"green": 0xff008000,
|
||||
"lime": 0xff00ff00,
|
||||
"olive": 0xff808000,
|
||||
"yellow": 0xffffff00,
|
||||
"navy": 0xff000080,
|
||||
"blue": 0xff0000ff,
|
||||
"teal": 0xff008080,
|
||||
"aqua": 0xff00ffff,
|
||||
"orange": 0xffffa500,
|
||||
"aliceblue": 0xfff0f8ff,
|
||||
"antiquewhite": 0xfffaebd7,
|
||||
"aquamarine": 0xff7fffd4,
|
||||
"azure": 0xfff0ffff,
|
||||
"beige": 0xfff5f5dc,
|
||||
"bisque": 0xffffe4c4,
|
||||
"blanchedalmond": 0xffffebcd,
|
||||
"blueviolet": 0xff8a2be2,
|
||||
"brown": 0xffa52a2a,
|
||||
"burlywood": 0xffdeb887,
|
||||
"cadetblue": 0xff5f9ea0,
|
||||
"chartreuse": 0xff7fff00,
|
||||
"chocolate": 0xffd2691e,
|
||||
"coral": 0xffff7f50,
|
||||
"cornflowerblue": 0xff6495ed,
|
||||
"cornsilk": 0xfffff8dc,
|
||||
"crimson": 0xffdc143c,
|
||||
"cyan": 0xff00ffff,
|
||||
"darkblue": 0xff00008b,
|
||||
"darkcyan": 0xff008b8b,
|
||||
"darkgoldenrod": 0xffb8860b,
|
||||
"darkgray": 0xffa9a9a9,
|
||||
"darkgreen": 0xff006400,
|
||||
"darkgrey": 0xffa9a9a9,
|
||||
"darkkhaki": 0xffbdb76b,
|
||||
"darkmagenta": 0xff8b008b,
|
||||
"darkolivegreen": 0xff556b2f,
|
||||
"darkorange": 0xffff8c00,
|
||||
"darkorchid": 0xff9932cc,
|
||||
"darkred": 0xff8b0000,
|
||||
"darksalmon": 0xffe9967a,
|
||||
"darkseagreen": 0xff8fbc8f,
|
||||
"darkslateblue": 0xff483d8b,
|
||||
"darkslategray": 0xff2f4f4f,
|
||||
"darkslategrey": 0xff2f4f4f,
|
||||
"darkturquoise": 0xff00ced1,
|
||||
"darkviolet": 0xff9400d3,
|
||||
"deeppink": 0xffff1493,
|
||||
"deepskyblue": 0xff00bfff,
|
||||
"dimgray": 0xff696969,
|
||||
"dimgrey": 0xff696969,
|
||||
"dodgerblue": 0xff1e90ff,
|
||||
"firebrick": 0xffb22222,
|
||||
"floralwhite": 0xfffffaf0,
|
||||
"forestgreen": 0xff228b22,
|
||||
"gainsboro": 0xffdcdcdc,
|
||||
"ghostwhite": 0xfff8f8ff,
|
||||
"gold": 0xffffd700,
|
||||
"goldenrod": 0xffdaa520,
|
||||
"greenyellow": 0xffadff2f,
|
||||
"grey": 0xff808080,
|
||||
"honeydew": 0xfff0fff0,
|
||||
"hotpink": 0xffff69b4,
|
||||
"indianred": 0xffcd5c5c,
|
||||
"indigo": 0xff4b0082,
|
||||
"ivory": 0xfffffff0,
|
||||
"khaki": 0xfff0e68c,
|
||||
"lavender": 0xffe6e6fa,
|
||||
"lavenderblush": 0xfffff0f5,
|
||||
"lawngreen": 0xff7cfc00,
|
||||
"lemonchiffon": 0xfffffacd,
|
||||
"lightblue": 0xffadd8e6,
|
||||
"lightcoral": 0xfff08080,
|
||||
"lightcyan": 0xffe0ffff,
|
||||
"lightgoldenrodyellow": 0xfffafad2,
|
||||
"lightgray": 0xffd3d3d3,
|
||||
"lightgreen": 0xff90ee90,
|
||||
"lightgrey": 0xffd3d3d3,
|
||||
"lightpink": 0xffffb6c1,
|
||||
"lightsalmon": 0xffffa07a,
|
||||
"lightseagreen": 0xff20b2aa,
|
||||
"lightskyblue": 0xff87cefa,
|
||||
"lightslategray": 0xff778899,
|
||||
"lightslategrey": 0xff778899,
|
||||
"lightsteelblue": 0xffb0c4de,
|
||||
"lightyellow": 0xffffffe0,
|
||||
"limegreen": 0xff32cd32,
|
||||
"linen": 0xfffaf0e6,
|
||||
"magenta": 0xffff00ff,
|
||||
"mediumaquamarine": 0xff66cdaa,
|
||||
"mediumblue": 0xff0000cd,
|
||||
"mediumorchid": 0xffba55d3,
|
||||
"mediumpurple": 0xff9370db,
|
||||
"mediumseagreen": 0xff3cb371,
|
||||
"mediumslateblue": 0xff7b68ee,
|
||||
"mediumspringgreen": 0xff00fa9a,
|
||||
"mediumturquoise": 0xff48d1cc,
|
||||
"mediumvioletred": 0xffc71585,
|
||||
"midnightblue": 0xff191970,
|
||||
"mintcream": 0xfff5fffa,
|
||||
"mistyrose": 0xffffe4e1,
|
||||
"moccasin": 0xffffe4b5,
|
||||
"navajowhite": 0xffffdead,
|
||||
"oldlace": 0xfffdf5e6,
|
||||
"olivedrab": 0xff6b8e23,
|
||||
"orangered": 0xffff4500,
|
||||
"orchid": 0xffda70d6,
|
||||
"palegoldenrod": 0xffeee8aa,
|
||||
"palegreen": 0xff98fb98,
|
||||
"paleturquoise": 0xffafeeee,
|
||||
"palevioletred": 0xffdb7093,
|
||||
"papayawhip": 0xffffefd5,
|
||||
"peachpuff": 0xffffdab9,
|
||||
"peru": 0xffcd853f,
|
||||
"pink": 0xffffc0cb,
|
||||
"plum": 0xffdda0dd,
|
||||
"powderblue": 0xffb0e0e6,
|
||||
"rosybrown": 0xffbc8f8f,
|
||||
"royalblue": 0xff4169e1,
|
||||
"saddlebrown": 0xff8b4513,
|
||||
"salmon": 0xfffa8072,
|
||||
"sandybrown": 0xfff4a460,
|
||||
"seagreen": 0xff2e8b57,
|
||||
"seashell": 0xfffff5ee,
|
||||
"sienna": 0xffa0522d,
|
||||
"skyblue": 0xff87ceeb,
|
||||
"slateblue": 0xff6a5acd,
|
||||
"slategray": 0xff708090,
|
||||
"slategrey": 0xff708090,
|
||||
"snow": 0xfffffafa,
|
||||
"springgreen": 0xff00ff7f,
|
||||
"steelblue": 0xff4682b4,
|
||||
"tan": 0xffd2b48c,
|
||||
"thistle": 0xffd8bfd8,
|
||||
"tomato": 0xffff6347,
|
||||
"turquoise": 0xff40e0d0,
|
||||
"violet": 0xffee82ee,
|
||||
"wheat": 0xfff5deb3,
|
||||
"whitesmoke": 0xfff5f5f5,
|
||||
"yellowgreen": 0xff9acd32,
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ColorChangedEvent = "color-changed"
|
||||
ColorPickerValue = "color-picker-value"
|
||||
)
|
||||
|
||||
// ColorPicker - ColorPicker view
|
||||
type ColorPicker interface {
|
||||
View
|
||||
}
|
||||
|
||||
type colorPickerData struct {
|
||||
viewData
|
||||
colorChangedListeners []func(ColorPicker, Color)
|
||||
}
|
||||
|
||||
// NewColorPicker create new ColorPicker object and return it
|
||||
func NewColorPicker(session Session, params Params) ColorPicker {
|
||||
view := new(colorPickerData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newColorPicker(session Session) View {
|
||||
return NewColorPicker(session, nil)
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) Init(session Session) {
|
||||
picker.viewData.Init(session)
|
||||
picker.tag = "ColorPicker"
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color){}
|
||||
picker.properties[Padding] = Px(0)
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case Value, ColorProperty:
|
||||
return ColorPickerValue
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) Remove(tag string) {
|
||||
picker.remove(picker.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) remove(tag string) {
|
||||
switch tag {
|
||||
case ColorChangedEvent:
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color){}
|
||||
|
||||
case ColorPickerValue:
|
||||
oldColor := GetColorPickerValue(picker, "")
|
||||
delete(picker.properties, ColorPickerValue)
|
||||
picker.colorChanged(oldColor)
|
||||
|
||||
default:
|
||||
picker.viewData.remove(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) Set(tag string, value interface{}) bool {
|
||||
return picker.set(picker.normalizeTag(tag), value)
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) set(tag string, value interface{}) bool {
|
||||
if value == nil {
|
||||
picker.remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case ColorChangedEvent:
|
||||
switch value := value.(type) {
|
||||
case func(ColorPicker, Color):
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color){value}
|
||||
|
||||
case func(Color):
|
||||
fn := func(view ColorPicker, date Color) {
|
||||
value(date)
|
||||
}
|
||||
picker.colorChangedListeners = []func(ColorPicker, Color){fn}
|
||||
|
||||
case []func(ColorPicker, Color):
|
||||
picker.colorChangedListeners = value
|
||||
|
||||
case []func(Color):
|
||||
listeners := make([]func(ColorPicker, Color), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
notCompatibleType(tag, val)
|
||||
return false
|
||||
}
|
||||
|
||||
listeners[i] = func(view ColorPicker, date Color) {
|
||||
val(date)
|
||||
}
|
||||
}
|
||||
picker.colorChangedListeners = listeners
|
||||
|
||||
case []interface{}:
|
||||
listeners := make([]func(ColorPicker, Color), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
notCompatibleType(tag, val)
|
||||
return false
|
||||
}
|
||||
|
||||
switch val := val.(type) {
|
||||
case func(ColorPicker, Color):
|
||||
listeners[i] = val
|
||||
|
||||
case func(Color):
|
||||
listeners[i] = func(view ColorPicker, date Color) {
|
||||
val(date)
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, val)
|
||||
return false
|
||||
}
|
||||
}
|
||||
picker.colorChangedListeners = listeners
|
||||
}
|
||||
return true
|
||||
|
||||
case ColorPickerValue:
|
||||
oldColor := GetColorPickerValue(picker, "")
|
||||
if picker.setColorProperty(ColorPickerValue, value) {
|
||||
newValue := GetColorPickerValue(picker, "")
|
||||
if oldColor != newValue {
|
||||
picker.colorChanged(oldColor)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
default:
|
||||
return picker.viewData.set(tag, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) colorChanged(oldColor Color) {
|
||||
newColor := GetColorPickerValue(picker, "")
|
||||
if oldColor != newColor {
|
||||
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), newColor.rgbString()))
|
||||
for _, listener := range picker.colorChangedListeners {
|
||||
listener(picker, newColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) Get(tag string) interface{} {
|
||||
return picker.get(picker.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) get(tag string) interface{} {
|
||||
switch tag {
|
||||
case ColorChangedEvent:
|
||||
return picker.colorChangedListeners
|
||||
|
||||
default:
|
||||
return picker.viewData.get(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) htmlTag() string {
|
||||
return "input"
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
picker.viewData.htmlProperties(self, buffer)
|
||||
|
||||
buffer.WriteString(` type="color" value="`)
|
||||
buffer.WriteString(GetColorPickerValue(picker, "").rgbString())
|
||||
buffer.WriteByte('"')
|
||||
|
||||
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
||||
if IsDisabled(self) {
|
||||
buffer.WriteString(` disabled`)
|
||||
}
|
||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
||||
}
|
||||
|
||||
func (picker *colorPickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||
switch command {
|
||||
case "textChanged":
|
||||
if text, ok := data.PropertyValue("text"); ok {
|
||||
oldColor := GetColorPickerValue(picker, "")
|
||||
if color, ok := StringToColor(text); ok {
|
||||
picker.properties[ColorPickerValue] = color
|
||||
if color != oldColor {
|
||||
for _, listener := range picker.colorChangedListeners {
|
||||
listener(picker, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return picker.viewData.handleCommand(self, command, data)
|
||||
}
|
||||
|
||||
// GetColorPickerValue returns the value of ColorPicker subview.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetColorPickerValue(view View, subviewID string) Color {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if result, ok := colorStyledProperty(view, ColorPickerValue); ok {
|
||||
return result
|
||||
}
|
||||
for _, tag := range []string{Value, ColorProperty} {
|
||||
if value, ok := valueFromStyle(view, tag); ok {
|
||||
if result, ok := valueToColor(value, view.Session()); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetColorChangedListeners(view View, subviewID string) []func(ColorPicker, Color) {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if value := view.Get(ColorChangedEvent); value != nil {
|
||||
if listeners, ok := value.([]func(ColorPicker, Color)); ok {
|
||||
return listeners
|
||||
}
|
||||
}
|
||||
}
|
||||
return []func(ColorPicker, Color){}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestColorARGB(t *testing.T) {
|
||||
color := Color(0x7FFE8743)
|
||||
a, r, g, b := color.ARGB()
|
||||
if a != 0x7F {
|
||||
t.Error("a != 0x7F")
|
||||
}
|
||||
if r != 0xFE {
|
||||
t.Error("r != 0xFE")
|
||||
}
|
||||
if g != 0x87 {
|
||||
t.Error("g != 0x87")
|
||||
}
|
||||
if b != 0x43 {
|
||||
t.Error("b != 0x43")
|
||||
}
|
||||
|
||||
if color.Alpha() != 0x7F {
|
||||
t.Error("color.Alpha() != 0x7F")
|
||||
}
|
||||
|
||||
if color.Red() != 0xFE {
|
||||
t.Error("color.Red() != 0xFE")
|
||||
}
|
||||
|
||||
if color.Green() != 0x87 {
|
||||
t.Error("color.Green() != 0x87")
|
||||
}
|
||||
|
||||
if color.Blue() != 0x43 {
|
||||
t.Error("color.Blue() != 0x43")
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorSetValue(t *testing.T) {
|
||||
createTestLog(t, true)
|
||||
|
||||
testData := []struct{ src, result string }{
|
||||
{"#7F102040", "rgba(16,32,64,.50)"},
|
||||
{"#102040", "rgb(16,32,64)"},
|
||||
{"#8124", "rgba(17,34,68,.53)"},
|
||||
{"rgba(17,34,67,.5)", "rgba(17,34,67,.50)"},
|
||||
{"rgb(.25,50%,96)", "rgb(63,127,96)"},
|
||||
{"rgba(.25,50%,96,100%)", "rgb(63,127,96)"},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
color, ok := StringToColor(data.src)
|
||||
if !ok {
|
||||
t.Errorf(`color.SetValue("%s") fail`, data.src)
|
||||
}
|
||||
result := color.cssString()
|
||||
if result != data.result {
|
||||
t.Errorf(`color.cssString() = "%s", expected: "%s"`, result, data.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorWriteData(t *testing.T) {
|
||||
testCSS := func(t *testing.T, color Color, result string) {
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.WriteString(color.cssString())
|
||||
str := buffer.String()
|
||||
if str != result {
|
||||
t.Errorf("color = %#X, expected = \"%s\", result = \"%s\"", color, result, str)
|
||||
}
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
color := Color(0x7FFE8743)
|
||||
color.writeData(buffer)
|
||||
str := buffer.String()
|
||||
if str != "#7FFE8743" {
|
||||
t.Errorf(`color = %#X, expected = "#7FFE8743", result = "%s"`, color, str)
|
||||
}
|
||||
|
||||
testCSS(t, Color(0x7FFE8743), "rgba(254,135,67,.50)")
|
||||
testCSS(t, Color(0xFFFE8743), "rgb(254,135,67)")
|
||||
testCSS(t, Color(0x05FE8743), "rgba(254,135,67,.02)")
|
||||
}
|
||||
|
||||
func TestColorSetData(t *testing.T) {
|
||||
test := func(t *testing.T, data string, result Color) {
|
||||
color, ok := StringToColor(data)
|
||||
if !ok {
|
||||
t.Errorf("data = \"%s\", fail result", data)
|
||||
} else if color != result {
|
||||
t.Errorf("data = \"%s\", expected = %#X, result = %#X", data, result, color)
|
||||
}
|
||||
}
|
||||
|
||||
test(t, "#7Ffe8743", 0x7FFE8743)
|
||||
test(t, "#fE8743", 0xFFFE8743)
|
||||
test(t, "#AE43", 0xAAEE4433)
|
||||
test(t, "#E43", 0xFFEE4433)
|
||||
|
||||
failData := []string{
|
||||
"",
|
||||
"7FfeG743",
|
||||
"#7Ffe87439",
|
||||
"#7FfeG743",
|
||||
"#7Ffe874",
|
||||
"#feG743",
|
||||
"#7Ffe8",
|
||||
"#fG73",
|
||||
"#GF3",
|
||||
}
|
||||
|
||||
for _, data := range failData {
|
||||
if color, ok := StringToColor(data); ok {
|
||||
t.Errorf("data = \"%s\", success, result = %#X", data, color)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ColumnCount is the constant for the "column-count" property tag.
|
||||
// The "column-count" int property specifies number of columns into which the content is break
|
||||
// Values less than zero are not valid. if the "column-count" property value is 0 then
|
||||
// the number of columns is calculated based on the "column-width" property
|
||||
ColumnCount = "column-count"
|
||||
// ColumnWidth is the constant for the "column-width" property tag.
|
||||
// The "column-width" SizeUnit property specifies the width of each column.
|
||||
ColumnWidth = "column-width"
|
||||
// ColumnGap is the constant for the "column-gap" property tag.
|
||||
// The "column-width" SizeUnit property sets the size of the gap (gutter) between columns.
|
||||
ColumnGap = "column-gap"
|
||||
// ColumnSeparator is the constant for the "column-separator" property tag.
|
||||
// The "column-separator" property specifies the line drawn between columns in a multi-column layout.
|
||||
ColumnSeparator = "column-separator"
|
||||
// ColumnSeparatorStyle is the constant for the "column-separator-style" property tag.
|
||||
// The "column-separator-style" int property sets the style of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
// Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4).
|
||||
ColumnSeparatorStyle = "column-separator-style"
|
||||
// ColumnSeparatorWidth is the constant for the "column-separator-width" property tag.
|
||||
// The "column-separator-width" SizeUnit property sets the width of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
ColumnSeparatorWidth = "column-separator-width"
|
||||
// ColumnSeparatorColor is the constant for the "column-separator-color" property tag.
|
||||
// The "column-separator-color" Color property sets the color of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
ColumnSeparatorColor = "column-separator-color"
|
||||
)
|
||||
|
||||
// ColumnLayout - grid-container of View
|
||||
type ColumnLayout interface {
|
||||
ViewsContainer
|
||||
}
|
||||
|
||||
type columnLayoutData struct {
|
||||
viewsContainerData
|
||||
}
|
||||
|
||||
// NewColumnLayout create new ColumnLayout object and return it
|
||||
func NewColumnLayout(session Session, params Params) ColumnLayout {
|
||||
view := new(columnLayoutData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newColumnLayout(session Session) View {
|
||||
return NewColumnLayout(session, nil)
|
||||
}
|
||||
|
||||
// Init initialize fields of ColumnLayout by default values
|
||||
func (ColumnLayout *columnLayoutData) Init(session Session) {
|
||||
ColumnLayout.viewsContainerData.Init(session)
|
||||
ColumnLayout.tag = "ColumnLayout"
|
||||
//ColumnLayout.systemClass = "ruiColumnLayout"
|
||||
}
|
||||
|
||||
func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case Gap:
|
||||
return ColumnGap
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func (columnLayout *columnLayoutData) Get(tag string) interface{} {
|
||||
return columnLayout.get(columnLayout.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (columnLayout *columnLayoutData) Remove(tag string) {
|
||||
columnLayout.remove(columnLayout.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (columnLayout *columnLayoutData) remove(tag string) {
|
||||
columnLayout.viewsContainerData.remove(tag)
|
||||
switch tag {
|
||||
case ColumnCount, ColumnWidth, ColumnGap:
|
||||
updateCSSProperty(columnLayout.htmlID(), tag, "", columnLayout.Session())
|
||||
|
||||
case ColumnSeparator:
|
||||
updateCSSProperty(columnLayout.htmlID(), "column-rule", "", columnLayout.Session())
|
||||
}
|
||||
}
|
||||
|
||||
func (columnLayout *columnLayoutData) Set(tag string, value interface{}) bool {
|
||||
return columnLayout.set(columnLayout.normalizeTag(tag), value)
|
||||
}
|
||||
|
||||
func (columnLayout *columnLayoutData) set(tag string, value interface{}) bool {
|
||||
if value == nil {
|
||||
columnLayout.remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case ColumnCount:
|
||||
if columnLayout.setIntProperty(tag, value) {
|
||||
session := columnLayout.Session()
|
||||
if count, ok := intProperty(columnLayout, tag, session, 0); ok && count > 0 {
|
||||
updateCSSProperty(columnLayout.htmlID(), tag, strconv.Itoa(count), session)
|
||||
} else {
|
||||
updateCSSProperty(columnLayout.htmlID(), tag, "auto", session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ok := columnLayout.viewsContainerData.set(tag, value)
|
||||
if ok {
|
||||
switch tag {
|
||||
case ColumnSeparator:
|
||||
css := ""
|
||||
session := columnLayout.Session()
|
||||
if val, ok := columnLayout.properties[ColumnSeparator]; ok {
|
||||
separator := val.(ColumnSeparatorProperty)
|
||||
css = separator.cssValue(columnLayout.Session())
|
||||
}
|
||||
updateCSSProperty(columnLayout.htmlID(), "column-rule", css, session)
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetColumnCount returns int value which specifies number of columns into which the content of
|
||||
// ColumnLayout is break. If the return value is 0 then the number of columns is calculated
|
||||
// based on the "column-width" property.
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnCount(view View, subviewID string) int {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view == nil {
|
||||
return 0
|
||||
}
|
||||
result, _ := intStyledProperty(view, ColumnCount, 0)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetColumnWidth returns SizeUnit value which specifies the width of each column of ColumnLayout.
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnWidth(view View, subviewID string) SizeUnit {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view == nil {
|
||||
return AutoSize()
|
||||
}
|
||||
result, _ := sizeStyledProperty(view, ColumnWidth)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetColumnGap returns SizeUnit property which specifies the size of the gap (gutter) between columns of ColumnLayout.
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnGap(view View, subviewID string) SizeUnit {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view == nil {
|
||||
return AutoSize()
|
||||
}
|
||||
result, _ := sizeStyledProperty(view, ColumnGap)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetColumnSeparator returns ViewBorder struct which specifies the line drawn between
|
||||
// columns in a multi-column ColumnLayout.
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnSeparator(view View, subviewID string) ViewBorder {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
|
||||
if view != nil {
|
||||
value := view.Get(ColumnSeparator)
|
||||
if value == nil {
|
||||
value, _ = valueFromStyle(view, ColumnSeparator)
|
||||
}
|
||||
|
||||
if value != nil {
|
||||
if separator, ok := value.(ColumnSeparatorProperty); ok {
|
||||
return separator.ViewBorder(view.Session())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ViewBorder{}
|
||||
}
|
||||
|
||||
// ColumnSeparatorStyle returns int value which specifies the style of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
// Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4).
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnSeparatorStyle(view View, subviewID string) int {
|
||||
border := GetColumnSeparator(view, subviewID)
|
||||
return border.Style
|
||||
}
|
||||
|
||||
// ColumnSeparatorWidth returns SizeUnit value which specifies the width of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnSeparatorWidth(view View, subviewID string) SizeUnit {
|
||||
border := GetColumnSeparator(view, subviewID)
|
||||
return border.Width
|
||||
}
|
||||
|
||||
// ColumnSeparatorColor returns Color value which specifies the color of the line drawn between
|
||||
// columns in a multi-column layout.
|
||||
// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned
|
||||
func GetColumnSeparatorColor(view View, subviewID string) Color {
|
||||
border := GetColumnSeparator(view, subviewID)
|
||||
return border.Color
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ColumnSeparatorProperty is the interface of a view separator data
|
||||
type ColumnSeparatorProperty interface {
|
||||
Properties
|
||||
ruiStringer
|
||||
fmt.Stringer
|
||||
ViewBorder(session Session) ViewBorder
|
||||
cssValue(session Session) string
|
||||
}
|
||||
|
||||
type columnSeparatorProperty struct {
|
||||
propertyList
|
||||
}
|
||||
|
||||
func newColumnSeparatorProperty(value interface{}) ColumnSeparatorProperty {
|
||||
|
||||
if value == nil {
|
||||
separator := new(columnSeparatorProperty)
|
||||
separator.properties = map[string]interface{}{}
|
||||
return separator
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case ColumnSeparatorProperty:
|
||||
return value
|
||||
|
||||
case DataObject:
|
||||
separator := new(columnSeparatorProperty)
|
||||
separator.properties = map[string]interface{}{}
|
||||
for _, tag := range []string{Style, Width, ColorProperty} {
|
||||
if val, ok := value.PropertyValue(tag); ok && val != "" {
|
||||
separator.set(tag, value)
|
||||
}
|
||||
}
|
||||
return separator
|
||||
|
||||
case ViewBorder:
|
||||
separator := new(columnSeparatorProperty)
|
||||
separator.properties = map[string]interface{}{
|
||||
Style: value.Style,
|
||||
Width: value.Width,
|
||||
ColorProperty: value.Color,
|
||||
}
|
||||
return separator
|
||||
}
|
||||
|
||||
invalidPropertyValue(Border, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewColumnSeparator creates the new ColumnSeparatorProperty
|
||||
func NewColumnSeparator(params Params) ColumnSeparatorProperty {
|
||||
separator := new(columnSeparatorProperty)
|
||||
separator.properties = map[string]interface{}{}
|
||||
if params != nil {
|
||||
for _, tag := range []string{Style, Width, ColorProperty} {
|
||||
if value, ok := params[tag]; ok && value != nil {
|
||||
separator.Set(tag, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return separator
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case ColumnSeparatorStyle, "separator-style":
|
||||
return Style
|
||||
|
||||
case ColumnSeparatorWidth, "separator-width":
|
||||
return Width
|
||||
|
||||
case ColumnSeparatorColor, "separator-color":
|
||||
return ColorProperty
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) ruiString(writer ruiWriter) {
|
||||
writer.startObject("_")
|
||||
for _, tag := range []string{Style, Width, ColorProperty} {
|
||||
if value, ok := separator.properties[tag]; ok {
|
||||
writer.writeProperty(Style, value)
|
||||
}
|
||||
}
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) String() string {
|
||||
writer := newRUIWriter()
|
||||
separator.ruiString(writer)
|
||||
return writer.finish()
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) Remove(tag string) {
|
||||
|
||||
switch tag = separator.normalizeTag(tag); tag {
|
||||
case Style, Width, ColorProperty:
|
||||
delete(separator.properties, tag)
|
||||
|
||||
default:
|
||||
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) Set(tag string, value interface{}) bool {
|
||||
tag = separator.normalizeTag(tag)
|
||||
|
||||
if value == nil {
|
||||
separator.remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case Style:
|
||||
return separator.setEnumProperty(Style, value, enumProperties[BorderStyle].values)
|
||||
|
||||
case Width:
|
||||
return separator.setSizeProperty(Width, value)
|
||||
|
||||
case ColorProperty:
|
||||
return separator.setColorProperty(ColorProperty, value)
|
||||
}
|
||||
|
||||
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
|
||||
return false
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) Get(tag string) interface{} {
|
||||
tag = separator.normalizeTag(tag)
|
||||
|
||||
if result, ok := separator.properties[tag]; ok {
|
||||
return result
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) ViewBorder(session Session) ViewBorder {
|
||||
style, _ := valueToEnum(separator.getRaw(Style), BorderStyle, session, NoneLine)
|
||||
width, _ := sizeProperty(separator, Width, session)
|
||||
color, _ := colorProperty(separator, ColorProperty, session)
|
||||
|
||||
return ViewBorder{
|
||||
Style: style,
|
||||
Width: width,
|
||||
Color: color,
|
||||
}
|
||||
}
|
||||
|
||||
func (separator *columnSeparatorProperty) cssValue(session Session) string {
|
||||
value := separator.ViewBorder(session)
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
if value.Width.Type != Auto && value.Width.Type != SizeInFraction && value.Width.Value > 0 {
|
||||
buffer.WriteString(value.Width.cssString(""))
|
||||
}
|
||||
|
||||
styles := enumProperties[BorderStyle].cssValues
|
||||
if value.Style > 0 && value.Style < len(styles) {
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteRune(' ')
|
||||
}
|
||||
buffer.WriteString(styles[value.Style])
|
||||
}
|
||||
|
||||
if value.Color != 0 {
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteRune(' ')
|
||||
}
|
||||
buffer.WriteString(value.Color.cssString())
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var systemStyles = map[string]string{
|
||||
"ruiApp": "body",
|
||||
"ruiDefault": "div",
|
||||
"ruiArticle": "article",
|
||||
"ruiSection": "section",
|
||||
"ruiAside": "aside",
|
||||
"ruiHeader": "header",
|
||||
"ruiMain": "main",
|
||||
"ruiFooter": "footer",
|
||||
"ruiNavigation": "nav",
|
||||
"ruiFigure": "figure",
|
||||
"ruiFigureCaption": "figcaption",
|
||||
"ruiButton": "button",
|
||||
"ruiP": "p",
|
||||
"ruiParagraph": "p",
|
||||
"ruiH1": "h1",
|
||||
"ruiH2": "h2",
|
||||
"ruiH3": "h3",
|
||||
"ruiH4": "h4",
|
||||
"ruiH5": "h5",
|
||||
"ruiH6": "h6",
|
||||
"ruiBlockquote": "blockquote",
|
||||
"ruiCode": "code",
|
||||
"ruiTable": "table",
|
||||
"ruiTableHead": "thead",
|
||||
"ruiTableFoot": "tfoot",
|
||||
"ruiTableRow": "tr",
|
||||
"ruiTableColumn": "col",
|
||||
"ruiTableCell": "td",
|
||||
"ruiDropDownList": "select",
|
||||
"ruiDropDownListItem": "option",
|
||||
}
|
||||
|
||||
var disabledStyles = []string{
|
||||
"ruiRoot",
|
||||
"ruiPopupLayer",
|
||||
"ruiAbsoluteLayout",
|
||||
"ruiGridLayout",
|
||||
"ruiListLayout",
|
||||
"ruiStackLayout",
|
||||
"ruiStackPageLayout",
|
||||
"ruiTabsLayout",
|
||||
"ruiImageView",
|
||||
"ruiListView",
|
||||
}
|
||||
|
||||
type cssBuilder interface {
|
||||
add(key, value string)
|
||||
addValues(key, separator string, values ...string)
|
||||
}
|
||||
|
||||
type viewCSSBuilder struct {
|
||||
buffer *strings.Builder
|
||||
}
|
||||
|
||||
type cssValueBuilder struct {
|
||||
buffer *strings.Builder
|
||||
}
|
||||
|
||||
type cssStyleBuilder struct {
|
||||
buffer *strings.Builder
|
||||
media bool
|
||||
}
|
||||
|
||||
func (builder *viewCSSBuilder) finish() string {
|
||||
if builder.buffer == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := builder.buffer.String()
|
||||
freeStringBuilder(builder.buffer)
|
||||
builder.buffer = nil
|
||||
return result
|
||||
}
|
||||
|
||||
func (builder *viewCSSBuilder) add(key, value string) {
|
||||
if value != "" {
|
||||
if builder.buffer == nil {
|
||||
builder.buffer = allocStringBuilder()
|
||||
} else if builder.buffer.Len() > 0 {
|
||||
builder.buffer.WriteRune(' ')
|
||||
}
|
||||
|
||||
builder.buffer.WriteString(key)
|
||||
builder.buffer.WriteString(": ")
|
||||
builder.buffer.WriteString(value)
|
||||
builder.buffer.WriteRune(';')
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *viewCSSBuilder) addValues(key, separator string, values ...string) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if builder.buffer == nil {
|
||||
builder.buffer = allocStringBuilder()
|
||||
} else if builder.buffer.Len() > 0 {
|
||||
builder.buffer.WriteRune(' ')
|
||||
}
|
||||
|
||||
builder.buffer.WriteString(key)
|
||||
builder.buffer.WriteString(": ")
|
||||
for i, value := range values {
|
||||
if i > 0 {
|
||||
builder.buffer.WriteString(separator)
|
||||
}
|
||||
builder.buffer.WriteString(value)
|
||||
}
|
||||
builder.buffer.WriteRune(';')
|
||||
}
|
||||
|
||||
func (builder *cssValueBuilder) finish() string {
|
||||
if builder.buffer == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := builder.buffer.String()
|
||||
freeStringBuilder(builder.buffer)
|
||||
builder.buffer = nil
|
||||
return result
|
||||
}
|
||||
|
||||
func (builder *cssValueBuilder) add(key, value string) {
|
||||
if value != "" {
|
||||
if builder.buffer == nil {
|
||||
builder.buffer = allocStringBuilder()
|
||||
}
|
||||
builder.buffer.WriteString(value)
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *cssValueBuilder) addValues(key, separator string, values ...string) {
|
||||
if len(values) > 0 {
|
||||
if builder.buffer == nil {
|
||||
builder.buffer = allocStringBuilder()
|
||||
}
|
||||
for i, value := range values {
|
||||
if i > 0 {
|
||||
builder.buffer.WriteString(separator)
|
||||
}
|
||||
builder.buffer.WriteString(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) init() {
|
||||
builder.buffer = allocStringBuilder()
|
||||
builder.buffer.Grow(16 * 1024)
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) finish() string {
|
||||
if builder.buffer == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := builder.buffer.String()
|
||||
freeStringBuilder(builder.buffer)
|
||||
builder.buffer = nil
|
||||
return result
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) startMedia(rule string) {
|
||||
if builder.buffer == nil {
|
||||
builder.init()
|
||||
}
|
||||
builder.buffer.WriteString(`@media screen`)
|
||||
builder.buffer.WriteString(rule)
|
||||
builder.buffer.WriteString(` {\n`)
|
||||
builder.media = true
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) endMedia() {
|
||||
if builder.buffer == nil {
|
||||
builder.init()
|
||||
}
|
||||
builder.buffer.WriteString(`}\n`)
|
||||
builder.media = false
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) startStyle(name string) {
|
||||
for _, disabledName := range disabledStyles {
|
||||
if name == disabledName {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if builder.buffer == nil {
|
||||
builder.init()
|
||||
}
|
||||
if builder.media {
|
||||
builder.buffer.WriteString(`\t`)
|
||||
}
|
||||
|
||||
if sysName, ok := systemStyles[name]; ok {
|
||||
builder.buffer.WriteString(sysName)
|
||||
} else {
|
||||
builder.buffer.WriteRune('.')
|
||||
builder.buffer.WriteString(name)
|
||||
}
|
||||
|
||||
builder.buffer.WriteString(` {\n`)
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) endStyle() {
|
||||
if builder.buffer == nil {
|
||||
builder.init()
|
||||
}
|
||||
if builder.media {
|
||||
builder.buffer.WriteString(`\t`)
|
||||
}
|
||||
builder.buffer.WriteString(`}\n`)
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) add(key, value string) {
|
||||
if value != "" {
|
||||
if builder.buffer == nil {
|
||||
builder.init()
|
||||
}
|
||||
if builder.media {
|
||||
builder.buffer.WriteString(`\t`)
|
||||
}
|
||||
builder.buffer.WriteString(`\t`)
|
||||
builder.buffer.WriteString(key)
|
||||
builder.buffer.WriteString(`: `)
|
||||
builder.buffer.WriteString(value)
|
||||
builder.buffer.WriteString(`;\n`)
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *cssStyleBuilder) addValues(key, separator string, values ...string) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if builder.buffer == nil {
|
||||
builder.init()
|
||||
}
|
||||
if builder.media {
|
||||
builder.buffer.WriteString(`\t`)
|
||||
}
|
||||
builder.buffer.WriteString(`\t`)
|
||||
builder.buffer.WriteString(key)
|
||||
builder.buffer.WriteString(`: `)
|
||||
for i, value := range values {
|
||||
if i > 0 {
|
||||
builder.buffer.WriteString(separator)
|
||||
}
|
||||
builder.buffer.WriteString(value)
|
||||
}
|
||||
builder.buffer.WriteString(`;\n`)
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
package rui
|
||||
|
||||
import "strings"
|
||||
|
||||
// CustomView defines a custom view interface
|
||||
type CustomView interface {
|
||||
ViewsContainer
|
||||
CreateSuperView(session Session) View
|
||||
SuperView() View
|
||||
setSuperView(view View)
|
||||
setTag(tag string)
|
||||
}
|
||||
|
||||
// CustomViewData defines a data of a basic custom view
|
||||
type CustomViewData struct {
|
||||
tag string
|
||||
superView View
|
||||
}
|
||||
|
||||
// InitCustomView initializes fields of CustomView by default values
|
||||
func InitCustomView(customView CustomView, tag string, session Session, params Params) bool {
|
||||
customView.setTag(tag)
|
||||
if view := customView.CreateSuperView(session); view != nil {
|
||||
customView.setSuperView(view)
|
||||
setInitParams(customView, params)
|
||||
} else {
|
||||
ErrorLog(`nil SuperView of "` + tag + `" view`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SuperView returns a super view
|
||||
func (customView *CustomViewData) SuperView() View {
|
||||
return customView.superView
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) setSuperView(view View) {
|
||||
customView.superView = view
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) setTag(tag string) {
|
||||
customView.tag = tag
|
||||
}
|
||||
|
||||
// Get returns a value of the property with name defined by the argument.
|
||||
// The type of return value depends on the property. If the property is not set then nil is returned.
|
||||
func (customView *CustomViewData) Get(tag string) interface{} {
|
||||
return customView.superView.Get(tag)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) getRaw(tag string) interface{} {
|
||||
return customView.superView.getRaw(tag)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) setRaw(tag string, value interface{}) {
|
||||
customView.superView.setRaw(tag, value)
|
||||
}
|
||||
|
||||
// Set sets the value (second argument) of the property with name defined by the first argument.
|
||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||
// a description of the error is written to the log
|
||||
func (customView *CustomViewData) Set(tag string, value interface{}) bool {
|
||||
return customView.superView.Set(tag, value)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) SetAnimated(tag string, value interface{}, animation Animation) bool {
|
||||
return customView.superView.SetAnimated(tag, value, animation)
|
||||
}
|
||||
|
||||
// Remove removes the property with name defined by the argument
|
||||
func (customView *CustomViewData) Remove(tag string) {
|
||||
customView.superView.Remove(tag)
|
||||
}
|
||||
|
||||
// AllTags returns an array of the set properties
|
||||
func (customView *CustomViewData) AllTags() []string {
|
||||
return customView.superView.AllTags()
|
||||
}
|
||||
|
||||
// Clear removes all properties
|
||||
func (customView *CustomViewData) Clear() {
|
||||
customView.superView.Clear()
|
||||
}
|
||||
|
||||
// Init initializes fields of View by default values
|
||||
func (customView *CustomViewData) Init(session Session) {
|
||||
}
|
||||
|
||||
// Session returns a current Session interface
|
||||
func (customView *CustomViewData) Session() Session {
|
||||
return customView.superView.Session()
|
||||
}
|
||||
|
||||
// Parent returns a parent view
|
||||
func (customView *CustomViewData) Parent() View {
|
||||
return customView.superView.Parent()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) parentHTMLID() string {
|
||||
return customView.superView.parentHTMLID()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) setParentID(parentID string) {
|
||||
customView.superView.setParentID(parentID)
|
||||
}
|
||||
|
||||
// Tag returns a tag of View interface
|
||||
func (customView *CustomViewData) Tag() string {
|
||||
if customView.tag != "" {
|
||||
return customView.tag
|
||||
}
|
||||
return customView.superView.Tag()
|
||||
}
|
||||
|
||||
// ID returns a id of the view
|
||||
func (customView *CustomViewData) ID() string {
|
||||
return customView.superView.ID()
|
||||
}
|
||||
|
||||
// Focusable returns true if the view receives the focus
|
||||
func (customView *CustomViewData) Focusable() bool {
|
||||
return customView.superView.Focusable()
|
||||
}
|
||||
|
||||
/*
|
||||
// SetTransitionEndListener sets the new listener of the transition end event
|
||||
func (customView *CustomViewData) SetTransitionEndListener(property string, listener TransitionEndListener) {
|
||||
customView.superView.SetTransitionEndListener(property, listener)
|
||||
}
|
||||
|
||||
// SetTransitionEndFunc sets the new listener function of the transition end event
|
||||
func (customView *CustomViewData) SetTransitionEndFunc(property string, listenerFunc func(View, string)) {
|
||||
customView.superView.SetTransitionEndFunc(property, listenerFunc)
|
||||
}
|
||||
*/
|
||||
|
||||
// Frame returns a location and size of the view in pixels
|
||||
func (customView *CustomViewData) Frame() Frame {
|
||||
return customView.superView.Frame()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) Scroll() Frame {
|
||||
return customView.superView.Scroll()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) onResize(self View, x, y, width, height float64) {
|
||||
customView.superView.onResize(customView.superView, x, y, width, height)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) onItemResize(self View, index int, x, y, width, height float64) {
|
||||
customView.superView.onItemResize(customView.superView, index, x, y, width, height)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) handleCommand(self View, command string, data DataObject) bool {
|
||||
return customView.superView.handleCommand(customView.superView, command, data)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) htmlClass(disabled bool) string {
|
||||
return customView.superView.htmlClass(disabled)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) htmlTag() string {
|
||||
return customView.superView.htmlTag()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) closeHTMLTag() bool {
|
||||
return customView.superView.closeHTMLTag()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) htmlID() string {
|
||||
return customView.superView.htmlID()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
customView.superView.htmlSubviews(customView.superView, buffer)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
customView.superView.htmlProperties(customView.superView, buffer)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
||||
customView.superView.htmlDisabledProperties(customView.superView, buffer)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
|
||||
customView.superView.cssStyle(customView.superView, builder)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) addToCSSStyle(addCSS map[string]string) {
|
||||
customView.superView.addToCSSStyle(addCSS)
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) setNoResizeEvent() {
|
||||
customView.superView.setNoResizeEvent()
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) isNoResizeEvent() bool {
|
||||
return customView.superView.isNoResizeEvent()
|
||||
}
|
||||
|
||||
// Views return a list of child views
|
||||
func (customView *CustomViewData) Views() []View {
|
||||
if customView.superView != nil {
|
||||
if container, ok := customView.superView.(ViewsContainer); ok {
|
||||
return container.Views()
|
||||
}
|
||||
}
|
||||
return []View{}
|
||||
}
|
||||
|
||||
// Append appends a view to the end of the list of a view children
|
||||
func (customView *CustomViewData) Append(view View) {
|
||||
if customView.superView != nil {
|
||||
if container, ok := customView.superView.(ViewsContainer); ok {
|
||||
container.Append(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts a view to the "index" position in the list of a view children
|
||||
func (customView *CustomViewData) Insert(view View, index uint) {
|
||||
if customView.superView != nil {
|
||||
if container, ok := customView.superView.(ViewsContainer); ok {
|
||||
container.Insert(view, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes a view from the list of a view children and return it
|
||||
func (customView *CustomViewData) RemoveView(index uint) View {
|
||||
if customView.superView != nil {
|
||||
if container, ok := customView.superView.(ViewsContainer); ok {
|
||||
return container.RemoveView(index)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) String() string {
|
||||
if customView.superView != nil {
|
||||
writer := newRUIWriter()
|
||||
customView.ruiString(writer)
|
||||
return writer.finish()
|
||||
}
|
||||
return customView.tag + " { }"
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) ruiString(writer ruiWriter) {
|
||||
if customView.superView != nil {
|
||||
ruiViewString(customView.superView, customView.tag, writer)
|
||||
}
|
||||
}
|
||||
|
||||
func (customView *CustomViewData) setScroll(x, y, width, height float64) {
|
||||
if customView.superView != nil {
|
||||
customView.superView.setScroll(x, y, width, height)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,631 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// DataValue interface of a data node value
|
||||
type DataValue interface {
|
||||
IsObject() bool
|
||||
Object() DataObject
|
||||
Value() string
|
||||
}
|
||||
|
||||
// DataObject interface of a data object
|
||||
type DataObject interface {
|
||||
DataValue
|
||||
Tag() string
|
||||
PropertyCount() int
|
||||
Property(index int) DataNode
|
||||
PropertyWithTag(tag string) DataNode
|
||||
PropertyValue(tag string) (string, bool)
|
||||
PropertyObject(tag string) DataObject
|
||||
SetPropertyValue(tag, value string)
|
||||
SetPropertyObject(tag string, object DataObject)
|
||||
}
|
||||
|
||||
const (
|
||||
// TextNode - node is the pair "tag - text value". Syntax: <tag> = <text>
|
||||
TextNode = 0
|
||||
// ObjectNode - node is the pair "tag - object". Syntax: <tag> = <object name>{...}
|
||||
ObjectNode = 1
|
||||
// ArrayNode - node is the pair "tag - object". Syntax: <tag> = [...]
|
||||
ArrayNode = 2
|
||||
)
|
||||
|
||||
// DataNode interface of a data node
|
||||
type DataNode interface {
|
||||
Tag() string
|
||||
Type() int
|
||||
Text() string
|
||||
Object() DataObject
|
||||
ArraySize() int
|
||||
ArrayElement(index int) DataValue
|
||||
ArrayElements() []DataValue
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
type dataStringValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (value *dataStringValue) Value() string {
|
||||
return value.value
|
||||
}
|
||||
|
||||
func (value *dataStringValue) IsObject() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (value *dataStringValue) Object() DataObject {
|
||||
return nil
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
type dataObject struct {
|
||||
tag string
|
||||
property []DataNode
|
||||
}
|
||||
|
||||
// NewDataObject create new DataObject with the tag and empty property list
|
||||
func NewDataObject(tag string) DataObject {
|
||||
obj := new(dataObject)
|
||||
obj.tag = tag
|
||||
obj.property = []DataNode{}
|
||||
return obj
|
||||
}
|
||||
|
||||
func (object *dataObject) Value() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (object *dataObject) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (object *dataObject) Object() DataObject {
|
||||
return object
|
||||
}
|
||||
|
||||
func (object *dataObject) Tag() string {
|
||||
return object.tag
|
||||
}
|
||||
|
||||
func (object *dataObject) PropertyCount() int {
|
||||
if object.property != nil {
|
||||
return len(object.property)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (object *dataObject) Property(index int) DataNode {
|
||||
if object.property == nil || index < 0 || index >= len(object.property) {
|
||||
return nil
|
||||
}
|
||||
return object.property[index]
|
||||
}
|
||||
|
||||
func (object *dataObject) PropertyWithTag(tag string) DataNode {
|
||||
if object.property != nil {
|
||||
for _, node := range object.property {
|
||||
if node.Tag() == tag {
|
||||
return node
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (object *dataObject) PropertyValue(tag string) (string, bool) {
|
||||
if node := object.PropertyWithTag(tag); node != nil && node.Type() == TextNode {
|
||||
return node.Text(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (object *dataObject) PropertyObject(tag string) DataObject {
|
||||
if node := object.PropertyWithTag(tag); node != nil && node.Type() == ObjectNode {
|
||||
return node.Object()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (object *dataObject) setNode(node DataNode) {
|
||||
if object.property == nil || len(object.property) == 0 {
|
||||
object.property = []DataNode{node}
|
||||
} else {
|
||||
tag := node.Tag()
|
||||
for i, p := range object.property {
|
||||
if p.Tag() == tag {
|
||||
object.property[i] = node
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
object.property = append(object.property, node)
|
||||
}
|
||||
}
|
||||
|
||||
// SetPropertyValue - set a string property with tag by value
|
||||
func (object *dataObject) SetPropertyValue(tag, value string) {
|
||||
val := new(dataStringValue)
|
||||
val.value = value
|
||||
node := new(dataNode)
|
||||
node.tag = tag
|
||||
node.value = val
|
||||
object.setNode(node)
|
||||
}
|
||||
|
||||
// SetPropertyObject - set a property with tag by object
|
||||
func (object *dataObject) SetPropertyObject(tag string, obj DataObject) {
|
||||
node := new(dataNode)
|
||||
node.tag = tag
|
||||
node.value = obj
|
||||
object.setNode(node)
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
type dataNode struct {
|
||||
tag string
|
||||
value DataValue
|
||||
array []DataValue
|
||||
}
|
||||
|
||||
func (node *dataNode) Tag() string {
|
||||
return node.tag
|
||||
}
|
||||
|
||||
func (node *dataNode) Type() int {
|
||||
if node.array != nil {
|
||||
return ArrayNode
|
||||
}
|
||||
if node.value.IsObject() {
|
||||
return ObjectNode
|
||||
}
|
||||
return TextNode
|
||||
}
|
||||
|
||||
func (node *dataNode) Text() string {
|
||||
if node.value != nil {
|
||||
return node.value.Value()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (node *dataNode) Object() DataObject {
|
||||
if node.value != nil {
|
||||
return node.value.Object()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *dataNode) ArraySize() int {
|
||||
if node.array != nil {
|
||||
return len(node.array)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (node *dataNode) ArrayElement(index int) DataValue {
|
||||
if node.array != nil && index >= 0 && index < len(node.array) {
|
||||
return node.array[index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *dataNode) ArrayElements() []DataValue {
|
||||
if node.array != nil {
|
||||
return node.array
|
||||
}
|
||||
return []DataValue{}
|
||||
}
|
||||
|
||||
// ParseDataText - parse text and return DataNode
|
||||
func ParseDataText(text string) DataObject {
|
||||
|
||||
if strings.ContainsAny(text, "\r") {
|
||||
text = strings.Replace(text, "\r\n", "\n", -1)
|
||||
text = strings.Replace(text, "\r", "\n", -1)
|
||||
}
|
||||
data := append([]rune(text), rune(0))
|
||||
pos := 0
|
||||
size := len(data) - 1
|
||||
line := 1
|
||||
lineStart := 0
|
||||
|
||||
skipSpaces := func(skipNewLine bool) {
|
||||
for pos < size {
|
||||
switch data[pos] {
|
||||
case '\n':
|
||||
if !skipNewLine {
|
||||
return
|
||||
}
|
||||
line++
|
||||
lineStart = pos + 1
|
||||
|
||||
case '/':
|
||||
if pos+1 < size {
|
||||
switch data[pos+1] {
|
||||
case '/':
|
||||
pos += 2
|
||||
for pos < size && data[pos] != '\n' {
|
||||
pos++
|
||||
}
|
||||
pos--
|
||||
|
||||
case '*':
|
||||
pos += 3
|
||||
for {
|
||||
if pos >= size {
|
||||
ErrorLog("Unexpected end of file")
|
||||
return
|
||||
}
|
||||
if data[pos-1] == '*' && data[pos] == '/' {
|
||||
break
|
||||
}
|
||||
if data[pos-1] == '\n' {
|
||||
line++
|
||||
lineStart = pos
|
||||
}
|
||||
pos++
|
||||
}
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case ' ', '\t':
|
||||
// do nothing
|
||||
|
||||
default:
|
||||
if !unicode.IsSpace(data[pos]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
parseTag := func() (string, bool) {
|
||||
skipSpaces(true)
|
||||
startPos := pos
|
||||
if data[pos] == '`' {
|
||||
pos++
|
||||
startPos++
|
||||
for data[pos] != '`' {
|
||||
pos++
|
||||
if pos >= size {
|
||||
ErrorLog("Unexpected end of text")
|
||||
return string(data[startPos:size]), false
|
||||
}
|
||||
}
|
||||
str := string(data[startPos:pos])
|
||||
pos++
|
||||
return str, true
|
||||
|
||||
} else if data[pos] == '\'' || data[pos] == '"' {
|
||||
|
||||
stopSymbol := data[pos]
|
||||
pos++
|
||||
startPos++
|
||||
slash := false
|
||||
for stopSymbol != data[pos] {
|
||||
if data[pos] == '\\' {
|
||||
pos += 2
|
||||
slash = true
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
if pos >= size {
|
||||
ErrorLog("Unexpected end of text")
|
||||
return string(data[startPos:size]), false
|
||||
}
|
||||
}
|
||||
|
||||
if !slash {
|
||||
str := string(data[startPos:pos])
|
||||
pos++
|
||||
skipSpaces(false)
|
||||
return str, true
|
||||
}
|
||||
|
||||
buffer := make([]rune, pos-startPos+1)
|
||||
n1 := 0
|
||||
n2 := startPos
|
||||
|
||||
invalidEscape := func() (string, bool) {
|
||||
str := string(data[startPos:pos])
|
||||
pos++
|
||||
ErrorLogF("Invalid escape sequence in \"%s\" (position %d)", str, n2-2-startPos)
|
||||
return str, false
|
||||
}
|
||||
|
||||
for n2 < pos {
|
||||
if data[n2] != '\\' {
|
||||
buffer[n1] = data[n2]
|
||||
n2++
|
||||
} else {
|
||||
n2 += 2
|
||||
switch data[n2-1] {
|
||||
case 'n':
|
||||
buffer[n1] = '\n'
|
||||
|
||||
case 'r':
|
||||
buffer[n1] = '\r'
|
||||
|
||||
case 't':
|
||||
buffer[n1] = '\t'
|
||||
|
||||
case '"':
|
||||
buffer[n1] = '"'
|
||||
|
||||
case '\'':
|
||||
buffer[n1] = '\''
|
||||
|
||||
case '\\':
|
||||
buffer[n1] = '\\'
|
||||
|
||||
case 'x', 'X':
|
||||
if n2+2 > pos {
|
||||
return invalidEscape()
|
||||
}
|
||||
x := 0
|
||||
for i := 0; i < 2; i++ {
|
||||
ch := data[n2]
|
||||
if ch >= '0' && ch <= '9' {
|
||||
x = x*16 + int(ch-'0')
|
||||
} else if ch >= 'a' && ch <= 'f' {
|
||||
x = x*16 + int(ch-'a'+10)
|
||||
} else if ch >= 'A' && ch <= 'F' {
|
||||
x = x*16 + int(ch-'A'+10)
|
||||
} else {
|
||||
return invalidEscape()
|
||||
}
|
||||
n2++
|
||||
}
|
||||
buffer[n1] = rune(x)
|
||||
|
||||
case 'u', 'U':
|
||||
if n2+4 > pos {
|
||||
return invalidEscape()
|
||||
}
|
||||
x := 0
|
||||
for i := 0; i < 4; i++ {
|
||||
ch := data[n2]
|
||||
if ch >= '0' && ch <= '9' {
|
||||
x = x*16 + int(ch-'0')
|
||||
} else if ch >= 'a' && ch <= 'f' {
|
||||
x = x*16 + int(ch-'a'+10)
|
||||
} else if ch >= 'A' && ch <= 'F' {
|
||||
x = x*16 + int(ch-'A'+10)
|
||||
} else {
|
||||
return invalidEscape()
|
||||
}
|
||||
n2++
|
||||
}
|
||||
buffer[n1] = rune(x)
|
||||
|
||||
default:
|
||||
str := string(data[startPos:pos])
|
||||
ErrorLogF("Invalid escape sequence in \"%s\" (position %d)", str, n2-2-startPos)
|
||||
return str, false
|
||||
}
|
||||
}
|
||||
n1++
|
||||
}
|
||||
|
||||
pos++
|
||||
skipSpaces(false)
|
||||
return string(buffer[0:n1]), true
|
||||
}
|
||||
|
||||
stopSymbol := func(symbol rune) bool {
|
||||
if unicode.IsSpace(symbol) {
|
||||
return true
|
||||
}
|
||||
for _, sym := range []rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'} {
|
||||
if sym == symbol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for pos < size && !stopSymbol(data[pos]) {
|
||||
pos++
|
||||
}
|
||||
|
||||
endPos := pos
|
||||
skipSpaces(false)
|
||||
if startPos == endPos {
|
||||
ErrorLog("empty tag")
|
||||
return "", false
|
||||
}
|
||||
return string(data[startPos:endPos]), true
|
||||
}
|
||||
|
||||
var parseObject func(tag string) DataObject
|
||||
var parseArray func() []DataValue
|
||||
|
||||
parseNode := func() DataNode {
|
||||
var tag string
|
||||
var ok bool
|
||||
|
||||
if tag, ok = parseTag(); !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
skipSpaces(true)
|
||||
if data[pos] != '=' {
|
||||
ErrorLogF("expected '=' after a tag name (line: %d, position: %d)", line, pos-lineStart)
|
||||
return nil
|
||||
}
|
||||
|
||||
pos++
|
||||
skipSpaces(true)
|
||||
switch data[pos] {
|
||||
case '[':
|
||||
node := new(dataNode)
|
||||
node.tag = tag
|
||||
|
||||
if node.array = parseArray(); node.array == nil {
|
||||
return nil
|
||||
}
|
||||
return node
|
||||
|
||||
case '{':
|
||||
node := new(dataNode)
|
||||
node.tag = tag
|
||||
if node.value = parseObject("_"); node.value == nil {
|
||||
return nil
|
||||
}
|
||||
return node
|
||||
|
||||
case '}', ']', '=':
|
||||
ErrorLogF("Expected '[', '{' or a tag name after '=' (line: %d, position: %d)", line, pos-lineStart)
|
||||
return nil
|
||||
|
||||
default:
|
||||
var str string
|
||||
if str, ok = parseTag(); !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
node := new(dataNode)
|
||||
node.tag = tag
|
||||
|
||||
if data[pos] == '{' {
|
||||
if node.value = parseObject(str); node.value == nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
val := new(dataStringValue)
|
||||
val.value = str
|
||||
node.value = val
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
parseObject = func(tag string) DataObject {
|
||||
if data[pos] != '{' {
|
||||
ErrorLogF("Expected '{' (line: %d, position: %d)", line, pos-lineStart)
|
||||
return nil
|
||||
}
|
||||
pos++
|
||||
|
||||
obj := new(dataObject)
|
||||
obj.tag = tag
|
||||
obj.property = []DataNode{}
|
||||
|
||||
for pos < size {
|
||||
var node DataNode
|
||||
|
||||
skipSpaces(true)
|
||||
if data[pos] == '}' {
|
||||
pos++
|
||||
skipSpaces(false)
|
||||
return obj
|
||||
}
|
||||
|
||||
if node = parseNode(); node == nil {
|
||||
return nil
|
||||
}
|
||||
obj.property = append(obj.property, node)
|
||||
if data[pos] == '}' {
|
||||
pos++
|
||||
skipSpaces(true)
|
||||
return obj
|
||||
} else if data[pos] != ',' && data[pos] != '\n' {
|
||||
ErrorLogF(`Expected '}', '\n' or ',' (line: %d, position: %d)`, line, pos-lineStart)
|
||||
return nil
|
||||
}
|
||||
if data[pos] != '\n' {
|
||||
pos++
|
||||
}
|
||||
skipSpaces(true)
|
||||
for data[pos] == ',' {
|
||||
pos++
|
||||
skipSpaces(true)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorLog("Unexpected end of text")
|
||||
return nil
|
||||
}
|
||||
|
||||
parseArray = func() []DataValue {
|
||||
pos++
|
||||
skipSpaces(true)
|
||||
|
||||
array := []DataValue{}
|
||||
|
||||
for pos < size {
|
||||
var tag string
|
||||
var ok bool
|
||||
|
||||
skipSpaces(true)
|
||||
for data[pos] == ',' && pos < size {
|
||||
pos++
|
||||
skipSpaces(true)
|
||||
}
|
||||
|
||||
if pos >= size {
|
||||
break
|
||||
}
|
||||
|
||||
if data[pos] == ']' {
|
||||
pos++
|
||||
skipSpaces(true)
|
||||
return array
|
||||
}
|
||||
|
||||
if tag, ok = parseTag(); !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data[pos] == '{' {
|
||||
obj := parseObject(tag)
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
array = append(array, obj)
|
||||
} else {
|
||||
val := new(dataStringValue)
|
||||
val.value = tag
|
||||
array = append(array, val)
|
||||
}
|
||||
|
||||
switch data[pos] {
|
||||
case ']', ',', '\n':
|
||||
|
||||
default:
|
||||
ErrorLogF("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
if data[pos] == ']' {
|
||||
pos++
|
||||
skipSpaces()
|
||||
return array, nil
|
||||
} else if data[pos] != ',' {
|
||||
return nil, fmt.Errorf("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart)
|
||||
}
|
||||
pos++
|
||||
skipSpaces()
|
||||
*/
|
||||
}
|
||||
|
||||
ErrorLog("Unexpected end of text")
|
||||
return nil
|
||||
}
|
||||
|
||||
if tag, ok := parseTag(); ok {
|
||||
return parseObject(tag)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package rui
|
||||
|
||||
/*
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDataWriter(t *testing.T) {
|
||||
w := NewDataWriter()
|
||||
w.StartObject("root")
|
||||
w.WriteStringKey("key1", "text")
|
||||
w.WriteStringKey("key2", "text 2")
|
||||
w.WriteStringKey("key 3", "text4")
|
||||
w.WriteStringsKey("key4", []string{"text4.1", "text4.2", "text4.3"}, '|')
|
||||
w.WriteStringsKey("key5", []string{"text5.1", "text5.2", "text5.3"}, ',')
|
||||
w.WriteColorKey("color", Color(0x7FD18243))
|
||||
w.WriteColorsKey("colors", []Color{Color(0x7FD18243), Color(0xFF817263)}, ',')
|
||||
w.WriteIntKey("int", 43)
|
||||
w.WriteIntsKey("ints", []int{111, 222, 333}, '|')
|
||||
|
||||
w.StartObjectKey("obj", "xxx")
|
||||
w.WriteSizeUnitKey("size", Px(16))
|
||||
w.WriteSizeUnitsKey("sizes", []SizeUnit{Px(8), Percent(100)}, ',')
|
||||
w.StartArray("array")
|
||||
w.WriteStringToArray("text")
|
||||
w.WriteColorToArray(Color(0x23456789))
|
||||
w.WriteIntToArray(1)
|
||||
w.WriteSizeUnitToArray(Inch(2))
|
||||
w.FinishArray()
|
||||
w.WriteBoundsKey("bounds1", Bounds{Px(8), Px(8), Px(8), Px(8)})
|
||||
w.WriteBoundsKey("bounds2", Bounds{Px(8), Pt(12), Mm(4.5), Inch(1.2)})
|
||||
w.FinishObject() // xxx
|
||||
|
||||
w.FinishObject() // root
|
||||
|
||||
text := w.String()
|
||||
expected := `root {
|
||||
key1 = text,
|
||||
key2 = "text 2",
|
||||
"key 3" = text4,
|
||||
key4 = text4.1|text4.2|text4.3,
|
||||
key5 = "text5.1,text5.2,text5.3",
|
||||
color = #7FD18243,
|
||||
colors = "#7FD18243,#FF817263",
|
||||
int = 43,
|
||||
ints = 111|222|333,
|
||||
obj = xxx {
|
||||
size = 16px,
|
||||
sizes = "8px,100%",
|
||||
array = [
|
||||
text,
|
||||
#23456789,
|
||||
1,
|
||||
2in
|
||||
],
|
||||
bounds1 = 8px,
|
||||
bounds2 = "8px,12pt,4.5mm,1.2in"
|
||||
}
|
||||
}`
|
||||
|
||||
if text != expected {
|
||||
t.Error("DataWriter test fail. Result:\n`" + text + "`\nExpected:\n`" + expected + "`")
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,211 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseDataText(t *testing.T) {
|
||||
|
||||
SetErrorLog(func(text string) {
|
||||
t.Error(text)
|
||||
})
|
||||
|
||||
text := `obj1 {
|
||||
key1 = val1,
|
||||
key2=obj2{
|
||||
key2.1=[val2.1,obj2.2{}, obj2.3{}],
|
||||
"key 2.2"='val 2.2'
|
||||
// Comment
|
||||
key2.3/* comment */ = {
|
||||
}
|
||||
/*
|
||||
Multiline comment
|
||||
*/
|
||||
'key2.4' = obj2.3{ text = " "},
|
||||
key2.5= [],
|
||||
},
|
||||
key3 = "\n \t \\ \r \" ' \X4F\x4e \U01Ea",` +
|
||||
"key4=`" + `\n \t \\ \r \" ' \x8F \UF80a` + "`\r}"
|
||||
|
||||
obj := ParseDataText(text)
|
||||
if obj != nil {
|
||||
if obj.Tag() != "obj1" {
|
||||
t.Error(`obj.Tag() != "obj1"`)
|
||||
}
|
||||
if !obj.IsObject() {
|
||||
t.Error(`!obj.IsObject()`)
|
||||
}
|
||||
if obj.PropertyCount() != 4 {
|
||||
t.Error(`obj.PropertyCount() != 4`)
|
||||
}
|
||||
|
||||
if obj.Property(-1) != nil {
|
||||
t.Error(`obj.Property(-1) != nil`)
|
||||
}
|
||||
|
||||
if val, ok := obj.PropertyValue("key1"); !ok || val != "val1" {
|
||||
t.Errorf(`obj.PropertyValue("key1") result: ("%s",%v)`, val, ok)
|
||||
}
|
||||
|
||||
if val, ok := obj.PropertyValue("key3"); !ok || val != "\n \t \\ \r \" ' \x4F\x4e \u01Ea" {
|
||||
t.Errorf(`obj.PropertyValue("key3") result: ("%s",%v)`, val, ok)
|
||||
}
|
||||
|
||||
if val, ok := obj.PropertyValue("key4"); !ok || val != `\n \t \\ \r \" ' \x8F \UF80a` {
|
||||
t.Errorf(`obj.PropertyValue("key4") result: ("%s",%v)`, val, ok)
|
||||
}
|
||||
|
||||
if o := obj.PropertyObject("key2"); o == nil {
|
||||
t.Error(`obj.PropertyObject("key2") == nil`)
|
||||
}
|
||||
|
||||
if o := obj.PropertyObject("key1"); o != nil {
|
||||
t.Error(`obj.PropertyObject("key1") != nil`)
|
||||
}
|
||||
|
||||
if o := obj.PropertyObject("key5"); o != nil {
|
||||
t.Error(`obj.PropertyObject("key5") != nil`)
|
||||
}
|
||||
|
||||
if val, ok := obj.PropertyValue("key2"); ok {
|
||||
t.Errorf(`obj.PropertyValue("key2") result: ("%s",%v)`, val, ok)
|
||||
}
|
||||
|
||||
if val, ok := obj.PropertyValue("key5"); ok {
|
||||
t.Errorf(`obj.PropertyValue("key5") result: ("%s",%v)`, val, ok)
|
||||
}
|
||||
|
||||
testKey := func(obj DataObject, index int, tag string, nodeType int) DataNode {
|
||||
key := obj.Property(index)
|
||||
if key == nil {
|
||||
t.Errorf(`%s.Property(%d) == nil`, obj.Tag(), index)
|
||||
} else {
|
||||
if key.Tag() != tag {
|
||||
t.Errorf(`%s.Property(%d).Tag() != "%s"`, obj.Tag(), index, tag)
|
||||
}
|
||||
|
||||
if key.Type() != nodeType {
|
||||
switch nodeType {
|
||||
case TextNode:
|
||||
t.Errorf(`%s.Property(%d) is not text`, obj.Tag(), index)
|
||||
|
||||
case ObjectNode:
|
||||
t.Errorf(`%s.Property(%d) is not object`, obj.Tag(), index)
|
||||
|
||||
case ArrayNode:
|
||||
t.Errorf(`%s.Property(%d) is not array`, obj.Tag(), index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
if key := testKey(obj, 0, "key1", TextNode); key != nil {
|
||||
if key.Text() != "val1" {
|
||||
t.Error(`key1.Value() != "val1"`)
|
||||
}
|
||||
}
|
||||
|
||||
if key := testKey(obj, 1, "key2", ObjectNode); key != nil {
|
||||
o := key.Object()
|
||||
if o == nil {
|
||||
t.Error(`key2.Value().Object() == nil`)
|
||||
} else {
|
||||
if o.PropertyCount() != 5 {
|
||||
t.Error(`key2.Value().Object().PropertyCount() != 4`)
|
||||
}
|
||||
|
||||
type testKeyData struct {
|
||||
tag string
|
||||
nodeType int
|
||||
}
|
||||
|
||||
data := []testKeyData{
|
||||
{tag: "key2.1", nodeType: ArrayNode},
|
||||
{tag: "key 2.2", nodeType: TextNode},
|
||||
{tag: "key2.3", nodeType: ObjectNode},
|
||||
{tag: "key2.4", nodeType: ObjectNode},
|
||||
{tag: "key2.5", nodeType: ArrayNode},
|
||||
}
|
||||
|
||||
for i, d := range data {
|
||||
testKey(o, i, d.tag, d.nodeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node1 := obj.Property(1)
|
||||
if node1 == nil {
|
||||
t.Error("obj.Property(1) != nil")
|
||||
} else if node1.Type() != ObjectNode {
|
||||
t.Error("obj.Property(1).Type() != ObjectNode")
|
||||
} else if obj := node1.Object(); obj != nil {
|
||||
if key := obj.Property(0); key != nil {
|
||||
if key.Type() != ArrayNode {
|
||||
t.Error("obj.Property(1).Object().Property(0)..Type() != ArrayNode")
|
||||
} else {
|
||||
if key.ArraySize() != 3 {
|
||||
t.Error("obj.Property(1).Object().Property(0).ArraySize() != 3")
|
||||
}
|
||||
|
||||
if e := key.ArrayElement(0); e == nil {
|
||||
t.Error("obj.Property(1).Object().Property(0).ArrayElement(0) == nil")
|
||||
} else if e.IsObject() {
|
||||
t.Error("obj.Property(1).Object().Property(0).ArrayElement(0).IsObject() == true")
|
||||
}
|
||||
|
||||
if e := key.ArrayElement(2); e == nil {
|
||||
t.Error("obj.Property(1).Object().Property(0).ArrayElement(2) == nil")
|
||||
} else if !e.IsObject() {
|
||||
t.Error("obj.Property(1).Object().Property(0).ArrayElement(2).IsObject() == false")
|
||||
} else if e.Value() != "" {
|
||||
t.Error(`obj.Property(1).Object().Property(0).ArrayElement(2).Value() != ""`)
|
||||
}
|
||||
|
||||
if e := key.ArrayElement(3); e != nil {
|
||||
t.Error("obj.Property(1).Object().Property(0).ArrayElement(3) != nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Error("obj.Property(1).Object() == nil")
|
||||
}
|
||||
}
|
||||
|
||||
SetErrorLog(func(text string) {
|
||||
})
|
||||
|
||||
failText := []string{
|
||||
" ",
|
||||
"obj[]",
|
||||
"obj={}",
|
||||
"obj{key}",
|
||||
"obj{key=}",
|
||||
"obj{key=val",
|
||||
"obj{key=obj2{}",
|
||||
"obj{key=obj2{key2}}",
|
||||
"obj{key=\"val}",
|
||||
"obj{key=val\"}",
|
||||
"obj{key=\"val`}",
|
||||
"obj{key=[}}",
|
||||
"obj{key=[val",
|
||||
"obj{key=[val,",
|
||||
"obj{key=[obj2{]",
|
||||
`obj{key="""}`,
|
||||
`obj{key="\z"}`,
|
||||
`obj{key="\xG6"}`,
|
||||
`obj{key="\uG678"}`,
|
||||
`obj{key="\x6"}`,
|
||||
`obj{key="\u678"}`,
|
||||
`obj{key1=val1 key2=val2}`,
|
||||
`obj{key=//"\u678"}`,
|
||||
`obj{key="\u678" /*}`,
|
||||
}
|
||||
|
||||
for _, txt := range failText {
|
||||
if obj := ParseDataText(txt); obj != nil {
|
||||
t.Errorf("result ParseDataText(\"%s\") must be fail", txt)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,404 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DateChangedEvent = "date-changed"
|
||||
DatePickerMin = "date-picker-min"
|
||||
DatePickerMax = "date-picker-max"
|
||||
DatePickerStep = "date-picker-step"
|
||||
DatePickerValue = "date-picker-value"
|
||||
dateFormat = "2006-01-02"
|
||||
)
|
||||
|
||||
// DatePicker - DatePicker view
|
||||
type DatePicker interface {
|
||||
View
|
||||
}
|
||||
|
||||
type datePickerData struct {
|
||||
viewData
|
||||
dateChangedListeners []func(DatePicker, time.Time)
|
||||
}
|
||||
|
||||
// NewDatePicker create new DatePicker object and return it
|
||||
func NewDatePicker(session Session, params Params) DatePicker {
|
||||
view := new(datePickerData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newDatePicker(session Session) View {
|
||||
return NewDatePicker(session, nil)
|
||||
}
|
||||
|
||||
func (picker *datePickerData) Init(session Session) {
|
||||
picker.viewData.Init(session)
|
||||
picker.tag = "DatePicker"
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
||||
}
|
||||
|
||||
func (picker *datePickerData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case Type, Min, Max, Step, Value:
|
||||
return "date-picker-" + tag
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (picker *datePickerData) Remove(tag string) {
|
||||
picker.remove(picker.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (picker *datePickerData) remove(tag string) {
|
||||
switch tag {
|
||||
case DateChangedEvent:
|
||||
if len(picker.dateChangedListeners) > 0 {
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time){}
|
||||
}
|
||||
|
||||
case DatePickerMin:
|
||||
delete(picker.properties, DatePickerMin)
|
||||
removeProperty(picker.htmlID(), Min, picker.session)
|
||||
|
||||
case DatePickerMax:
|
||||
delete(picker.properties, DatePickerMax)
|
||||
removeProperty(picker.htmlID(), Max, picker.session)
|
||||
|
||||
case DatePickerStep:
|
||||
delete(picker.properties, DatePickerMax)
|
||||
removeProperty(picker.htmlID(), Step, picker.session)
|
||||
|
||||
case DatePickerValue:
|
||||
delete(picker.properties, DatePickerValue)
|
||||
updateProperty(picker.htmlID(), Value, time.Now().Format(dateFormat), picker.session)
|
||||
|
||||
default:
|
||||
picker.viewData.remove(tag)
|
||||
picker.propertyChanged(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (picker *datePickerData) Set(tag string, value interface{}) bool {
|
||||
return picker.set(picker.normalizeTag(tag), value)
|
||||
}
|
||||
|
||||
func (picker *datePickerData) set(tag string, value interface{}) bool {
|
||||
if value == nil {
|
||||
picker.remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
setTimeValue := func(tag string) (time.Time, bool) {
|
||||
//old, oldOK := getDateProperty(picker, tag, shortTag)
|
||||
switch value := value.(type) {
|
||||
case time.Time:
|
||||
picker.properties[tag] = value
|
||||
return value, true
|
||||
|
||||
case string:
|
||||
if text, ok := picker.Session().resolveConstants(value); ok {
|
||||
if date, err := time.Parse(dateFormat, text); err == nil {
|
||||
picker.properties[tag] = value
|
||||
return date, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notCompatibleType(tag, value)
|
||||
return time.Now(), false
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case DatePickerMin:
|
||||
old, oldOK := getDateProperty(picker, DatePickerMin, Min)
|
||||
if date, ok := setTimeValue(DatePickerMin); ok {
|
||||
if !oldOK || date != old {
|
||||
updateProperty(picker.htmlID(), Min, date.Format(dateFormat), picker.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case DatePickerMax:
|
||||
old, oldOK := getDateProperty(picker, DatePickerMax, Max)
|
||||
if date, ok := setTimeValue(DatePickerMax); ok {
|
||||
if !oldOK || date != old {
|
||||
updateProperty(picker.htmlID(), Max, date.Format(dateFormat), picker.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case DatePickerStep:
|
||||
oldStep := GetDatePickerStep(picker, "")
|
||||
if picker.setIntProperty(DatePickerStep, value) {
|
||||
step := GetDatePickerStep(picker, "")
|
||||
if oldStep != step {
|
||||
if step > 0 {
|
||||
updateProperty(picker.htmlID(), Step, strconv.Itoa(step), picker.session)
|
||||
} else {
|
||||
removeProperty(picker.htmlID(), Step, picker.session)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case DatePickerValue:
|
||||
oldDate := GetDatePickerValue(picker, "")
|
||||
if date, ok := setTimeValue(DatePickerMax); ok {
|
||||
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), date.Format(dateFormat)))
|
||||
if date != oldDate {
|
||||
for _, listener := range picker.dateChangedListeners {
|
||||
listener(picker, date)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
case DateChangedEvent:
|
||||
switch value := value.(type) {
|
||||
case func(DatePicker, time.Time):
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time){value}
|
||||
|
||||
case func(time.Time):
|
||||
fn := func(view DatePicker, date time.Time) {
|
||||
value(date)
|
||||
}
|
||||
picker.dateChangedListeners = []func(DatePicker, time.Time){fn}
|
||||
|
||||
case []func(DatePicker, time.Time):
|
||||
picker.dateChangedListeners = value
|
||||
|
||||
case []func(time.Time):
|
||||
listeners := make([]func(DatePicker, time.Time), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
notCompatibleType(tag, val)
|
||||
return false
|
||||
}
|
||||
|
||||
listeners[i] = func(view DatePicker, date time.Time) {
|
||||
val(date)
|
||||
}
|
||||
}
|
||||
picker.dateChangedListeners = listeners
|
||||
|
||||
case []interface{}:
|
||||
listeners := make([]func(DatePicker, time.Time), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
notCompatibleType(tag, val)
|
||||
return false
|
||||
}
|
||||
|
||||
switch val := val.(type) {
|
||||
case func(DatePicker, time.Time):
|
||||
listeners[i] = val
|
||||
|
||||
case func(time.Time):
|
||||
listeners[i] = func(view DatePicker, date time.Time) {
|
||||
val(date)
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, val)
|
||||
return false
|
||||
}
|
||||
}
|
||||
picker.dateChangedListeners = listeners
|
||||
}
|
||||
return true
|
||||
|
||||
default:
|
||||
if picker.viewData.set(tag, value) {
|
||||
picker.propertyChanged(tag)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (picker *datePickerData) Get(tag string) interface{} {
|
||||
return picker.get(picker.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (picker *datePickerData) get(tag string) interface{} {
|
||||
switch tag {
|
||||
case DateChangedEvent:
|
||||
return picker.dateChangedListeners
|
||||
|
||||
default:
|
||||
return picker.viewData.get(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (picker *datePickerData) htmlTag() string {
|
||||
return "input"
|
||||
}
|
||||
|
||||
func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
picker.viewData.htmlProperties(self, buffer)
|
||||
|
||||
buffer.WriteString(` type="date"`)
|
||||
|
||||
if min, ok := getDateProperty(picker, DatePickerMin, Min); ok {
|
||||
buffer.WriteString(` min="`)
|
||||
buffer.WriteString(min.Format(dateFormat))
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
|
||||
if max, ok := getDateProperty(picker, DatePickerMax, Max); ok {
|
||||
buffer.WriteString(` max="`)
|
||||
buffer.WriteString(max.Format(dateFormat))
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
|
||||
if step, ok := intProperty(picker, DatePickerStep, picker.Session(), 0); ok && step > 0 {
|
||||
buffer.WriteString(` step="`)
|
||||
buffer.WriteString(strconv.Itoa(step))
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
|
||||
buffer.WriteString(` value="`)
|
||||
buffer.WriteString(GetDatePickerValue(picker, "").Format(dateFormat))
|
||||
buffer.WriteByte('"')
|
||||
|
||||
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
||||
}
|
||||
|
||||
func (picker *datePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
||||
if IsDisabled(self) {
|
||||
buffer.WriteString(` disabled`)
|
||||
}
|
||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
||||
}
|
||||
|
||||
func (picker *datePickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||
switch command {
|
||||
case "textChanged":
|
||||
if text, ok := data.PropertyValue("text"); ok {
|
||||
if value, err := time.Parse(dateFormat, text); err == nil {
|
||||
oldValue := GetDatePickerValue(picker, "")
|
||||
picker.properties[DatePickerValue] = value
|
||||
if value != oldValue {
|
||||
for _, listener := range picker.dateChangedListeners {
|
||||
listener(picker, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return picker.viewData.handleCommand(self, command, data)
|
||||
}
|
||||
|
||||
func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||
valueToTime := func(value interface{}) (time.Time, bool) {
|
||||
if value != nil {
|
||||
switch value := value.(type) {
|
||||
case time.Time:
|
||||
return value, true
|
||||
|
||||
case string:
|
||||
if text, ok := view.Session().resolveConstants(value); ok {
|
||||
if result, err := time.Parse(dateFormat, text); err == nil {
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return time.Now(), false
|
||||
}
|
||||
|
||||
if view != nil {
|
||||
if result, ok := valueToTime(view.getRaw(mainTag)); ok {
|
||||
return result, true
|
||||
}
|
||||
|
||||
if value, ok := valueFromStyle(view, shortTag); ok {
|
||||
if result, ok := valueToTime(value); ok {
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return time.Now(), false
|
||||
}
|
||||
|
||||
// GetDatePickerMin returns the min date of DatePicker subview and "true" as the second value if the min date is set,
|
||||
// "false" as the second value otherwise.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetDatePickerMin(view View, subviewID string) (time.Time, bool) {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
return getDateProperty(view, DatePickerMin, Min)
|
||||
}
|
||||
return time.Now(), false
|
||||
}
|
||||
|
||||
// GetDatePickerMax returns the max date of DatePicker subview and "true" as the second value if the min date is set,
|
||||
// "false" as the second value otherwise.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetDatePickerMax(view View, subviewID string) (time.Time, bool) {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
return getDateProperty(view, DatePickerMax, Max)
|
||||
}
|
||||
return time.Now(), false
|
||||
}
|
||||
|
||||
// GetDatePickerStep returns the date changing step in days of DatePicker subview.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetDatePickerStep(view View, subviewID string) int {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if result, _ := intStyledProperty(view, DatePickerStep, 0); result >= 0 {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetDatePickerValue returns the date of DatePicker subview.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetDatePickerValue(view View, subviewID string) time.Time {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view == nil {
|
||||
return time.Now()
|
||||
}
|
||||
date, _ := getDateProperty(view, DatePickerValue, Value)
|
||||
return date
|
||||
}
|
||||
|
||||
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
|
||||
// If there are no listeners then the empty list is returned
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetDateChangedListeners(view View, subviewID string) []func(DatePicker, time.Time) {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if value := view.Get(DateChangedEvent); value != nil {
|
||||
if listeners, ok := value.([]func(DatePicker, time.Time)); ok {
|
||||
return listeners
|
||||
}
|
||||
}
|
||||
}
|
||||
return []func(DatePicker, time.Time){}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
theme {
|
||||
colors = _{
|
||||
ruiTextColor = #FF000000,
|
||||
ruiDisabledTextColor = #FF202020,
|
||||
ruiBackgroundColor = #FFFFFFFF,
|
||||
ruiButtonColor = #FFE0E0E0,
|
||||
ruiButtonActiveColor = #FFC0C0C0,
|
||||
ruiButtonTextColor = #FF000000,
|
||||
ruiButtonDisabledColor = #FFE0E0E0,
|
||||
ruiButtonDisabledTextColor = #FF202020,
|
||||
ruiHighlightColor = #FF1A74E8,
|
||||
ruiHighlightTextColor = #FFFFFFFF,
|
||||
ruiSelectedColor = #FFE0E0E0,
|
||||
ruiSelectedTextColor = #FF000000,
|
||||
ruiPopupBackgroundColor = #FFFFFFFF,
|
||||
ruiPopupTextColor = #FF000000,
|
||||
ruiPopupTitleColor = #FF0000FF,
|
||||
ruiPopupTitleTextColor = #FFFFFFFF,
|
||||
|
||||
ruiTabsBackgroundColor = #FFEEEEEE,
|
||||
ruiInactiveTabColor = #FFD0D0D0,
|
||||
ruiInactiveTabTextColor = #FF202020,
|
||||
ruiActiveTabColor = #FFFFFFFF,
|
||||
ruiActiveTabTextColor = #FF000000,
|
||||
},
|
||||
colors:dark = _{
|
||||
ruiTextColor = #FFE0E0E0,
|
||||
ruiDisabledTextColor = #FFA0A0A0,
|
||||
ruiBackgroundColor = #FF080808,
|
||||
ruiButtonColor = #FF404040,
|
||||
ruiButtonTextColor = #FFE0E0E0,
|
||||
ruiButtonDisabledColor = #FF404040,
|
||||
ruiButtonDisabledTextColor = #FFA0A0A0,
|
||||
ruiHighlightColor = #FF1A74E8,
|
||||
ruiHighlightTextColor = #FFFFFFFF,
|
||||
},
|
||||
constants = _{
|
||||
ruiButtonHorizontalPadding = 16px,
|
||||
ruiButtonVerticalPadding = 8px,
|
||||
ruiButtonMargin = 4px,
|
||||
ruiButtonRadius = 4px,
|
||||
ruiButtonHighlightDilation = 1.5px,
|
||||
ruiButtonHighlightBlur = 2px,
|
||||
ruiCheckboxGap = 12px,
|
||||
ruiListItemHorizontalPadding = 12px,
|
||||
ruiListItemVerticalPadding = 4px,
|
||||
ruiPopupTitleHeight = 32px,
|
||||
ruiPopupTitlePadding = 8px,
|
||||
ruiPopupButtonGap = 4px,
|
||||
ruiTabSpace = 2px,
|
||||
ruiTabHeight = 32px,
|
||||
ruiTabPadding = 2px,
|
||||
},
|
||||
constants:touch = _{
|
||||
ruiButtonHorizontalPadding = 20px,
|
||||
ruiButtonVerticalPadding = 16px
|
||||
},
|
||||
styles = [
|
||||
ruiApp {
|
||||
font-name = "Arial, Helvetica, sans-serif",
|
||||
text-size = 12pt,
|
||||
text-color = @ruiTextColor,
|
||||
background-color = @ruiBackgroundColor,
|
||||
},
|
||||
ruiButton {
|
||||
align = center,
|
||||
padding = "@ruiButtonVerticalPadding, @ruiButtonHorizontalPadding, @ruiButtonVerticalPadding, @ruiButtonHorizontalPadding",
|
||||
margin = @ruiButtonMargin,
|
||||
radius = @ruiButtonRadius,
|
||||
background-color = @ruiButtonColor,
|
||||
text-color = @ruiButtonTextColor,
|
||||
border = _{width = 1px, style = solid, color = @ruiButtonTextColor}
|
||||
},
|
||||
ruiDisabledButton {
|
||||
align = center,
|
||||
padding = "@ruiButtonVerticalPadding, @ruiButtonHorizontalPadding, @ruiButtonVerticalPadding, @ruiButtonHorizontalPadding",
|
||||
margin = @ruiButtonMargin,
|
||||
radius = @ruiButtonRadius,
|
||||
background-color = @ruiButtonDisabledColor,
|
||||
text-color = @ruiButtonDisabledTextColor,
|
||||
border = _{width = 1px, style = solid, color = @ruiButtonDisabledTextColor}
|
||||
},
|
||||
ruiButton:hover {
|
||||
text-color = @ruiTextColor,
|
||||
background-color = @ruiBackgroundColor,
|
||||
},
|
||||
ruiButton:focus {
|
||||
shadow = _{spread-radius = @ruiButtonHighlightDilation, blur = @ruiButtonHighlightBlur, color = @ruiHighlightColor },
|
||||
},
|
||||
ruiButton:active {
|
||||
background-color = @ruiButtonActiveColor
|
||||
},
|
||||
ruiCheckbox {
|
||||
radius = 2px,
|
||||
padding = 1px,
|
||||
margin = 2px,
|
||||
},
|
||||
ruiCheckbox:focus {
|
||||
margin = 0,
|
||||
border = _{style = solid, color = @ruiHighlightColor, width = 2px },
|
||||
},
|
||||
ruiListItem {
|
||||
radius = 4px,
|
||||
padding = "@ruiListItemVerticalPadding, @ruiListItemHorizontalPadding, @ruiListItemVerticalPadding, @ruiListItemHorizontalPadding",
|
||||
},
|
||||
ruiListItemSelected {
|
||||
background-color=@ruiSelectedColor,
|
||||
text-color=@ruiSelectedTextColor,
|
||||
},
|
||||
ruiListItemFocused {
|
||||
background-color=@ruiHighlightColor,
|
||||
text-color=@ruiHighlightTextColor,
|
||||
},
|
||||
ruiActiveTab {
|
||||
background-color = @ruiActiveTabColor,
|
||||
text-color = @ruiActiveTabTextColor,
|
||||
padding-left = 8px,
|
||||
padding-right = 8px,
|
||||
},
|
||||
ruiInactiveTab {
|
||||
background-color = @ruiInactiveTabColor,
|
||||
text-color = @ruiInactiveTabTextColor,
|
||||
padding-left = 8px,
|
||||
padding-right = 8px,
|
||||
},
|
||||
ruiActiveVerticalTab {
|
||||
background-color = @ruiActiveTabColor,
|
||||
text-color = @ruiActiveTabTextColor,
|
||||
padding-top = 8px,
|
||||
padding-bottom = 8px,
|
||||
},
|
||||
ruiInactiveVerticalTab {
|
||||
background-color = @ruiInactiveTabColor,
|
||||
text-color = @ruiInactiveTabTextColor,
|
||||
padding-top = 8px,
|
||||
padding-bottom = 8px,
|
||||
},
|
||||
ruiPopup {
|
||||
background-color = @ruiPopupBackgroundColor,
|
||||
text-color = @ruiPopupTextColor,
|
||||
radius = 4px,
|
||||
shadow = _{spread-radius=4px, blur=16px, color=#80808080},
|
||||
}
|
||||
ruiPopupTitle {
|
||||
background-color = @ruiPopupTitleColor,
|
||||
text-color = @ruiPopupTitleTextColor,
|
||||
min-height = 24px,
|
||||
}
|
||||
ruiMessageText {
|
||||
padding-left = 64px,
|
||||
padding-right = 64px,
|
||||
padding-top = 32px,
|
||||
padding-bottom = 32px,
|
||||
}
|
||||
ruiPopupMenuItem {
|
||||
padding-top = 4px,
|
||||
padding-bottom = 4px,
|
||||
padding-left = 8px,
|
||||
padding-right = 8px,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package rui
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
// Summary is the constant for the "summary" property tag.
|
||||
// The contents of the "summary" property are used as the label for the disclosure widget.
|
||||
Summary = "summary"
|
||||
// Expanded is the constant for the "expanded" property tag.
|
||||
// If the "expanded" boolean property is "true", then the content of view is visible.
|
||||
// If the value is "false" then the content is collapsed.
|
||||
Expanded = "expanded"
|
||||
)
|
||||
|
||||
// DetailsView - collapsible container of View
|
||||
type DetailsView interface {
|
||||
ViewsContainer
|
||||
}
|
||||
|
||||
type detailsViewData struct {
|
||||
viewsContainerData
|
||||
}
|
||||
|
||||
// NewDetailsView create new DetailsView object and return it
|
||||
func NewDetailsView(session Session, params Params) DetailsView {
|
||||
view := new(detailsViewData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newDetailsView(session Session) View {
|
||||
return NewDetailsView(session, nil)
|
||||
}
|
||||
|
||||
// Init initialize fields of DetailsView by default values
|
||||
func (detailsView *detailsViewData) Init(session Session) {
|
||||
detailsView.viewsContainerData.Init(session)
|
||||
detailsView.tag = "DetailsView"
|
||||
//detailsView.systemClass = "ruiDetailsView"
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) Remove(tag string) {
|
||||
detailsView.remove(strings.ToLower(tag))
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) remove(tag string) {
|
||||
if _, ok := detailsView.properties[tag]; ok {
|
||||
switch tag {
|
||||
case Summary:
|
||||
delete(detailsView.properties, tag)
|
||||
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
||||
|
||||
case Expanded:
|
||||
delete(detailsView.properties, tag)
|
||||
removeProperty(detailsView.htmlID(), "open", detailsView.Session())
|
||||
|
||||
default:
|
||||
detailsView.viewsContainerData.remove(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) Set(tag string, value interface{}) bool {
|
||||
return detailsView.set(strings.ToLower(tag), value)
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) set(tag string, value interface{}) bool {
|
||||
switch tag {
|
||||
case Summary:
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
detailsView.properties[Summary] = value
|
||||
|
||||
case View:
|
||||
detailsView.properties[Summary] = value
|
||||
|
||||
case DataObject:
|
||||
if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
|
||||
detailsView.properties[Summary] = view
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
||||
return true
|
||||
|
||||
case Expanded:
|
||||
if detailsView.setBoolProperty(tag, value) {
|
||||
if IsDetailsExpanded(detailsView, "") {
|
||||
updateProperty(detailsView.htmlID(), "open", "", detailsView.Session())
|
||||
} else {
|
||||
removeProperty(detailsView.htmlID(), "open", detailsView.Session())
|
||||
}
|
||||
return true
|
||||
}
|
||||
notCompatibleType(tag, value)
|
||||
return false
|
||||
}
|
||||
|
||||
return detailsView.viewsContainerData.Set(tag, value)
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) Get(tag string) interface{} {
|
||||
return detailsView.get(strings.ToLower(tag))
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) get(tag string) interface{} {
|
||||
return detailsView.viewsContainerData.get(tag)
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) htmlTag() string {
|
||||
return "details"
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
detailsView.viewsContainerData.htmlProperties(self, buffer)
|
||||
if IsDetailsExpanded(detailsView, "") {
|
||||
buffer.WriteString(` open`)
|
||||
}
|
||||
}
|
||||
|
||||
func (detailsView *detailsViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
if value, ok := detailsView.properties[Summary]; ok {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
buffer.WriteString("<summary>")
|
||||
buffer.WriteString(value)
|
||||
buffer.WriteString("</summary>")
|
||||
|
||||
case View:
|
||||
buffer.WriteString("<summary>")
|
||||
viewHTML(value, buffer)
|
||||
buffer.WriteString("</summary>")
|
||||
}
|
||||
}
|
||||
|
||||
detailsView.viewsContainerData.htmlSubviews(self, buffer)
|
||||
}
|
||||
|
||||
// GetDetailsSummary returns a value of the Summary property of DetailsView.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func GetDetailsSummary(view View, subviewID string) View {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if value := view.Get(Summary); value != nil {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
return NewTextView(view.Session(), Params{Text: value})
|
||||
|
||||
case View:
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDetailsExpanded returns a value of the Expanded property of DetailsView.
|
||||
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
|
||||
func IsDetailsExpanded(view View, subviewID string) bool {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if result, ok := boolStyledProperty(view, Expanded); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const DropDownEvent = "drop-down-event"
|
||||
|
||||
// DropDownList - the interface of a drop-down list view
|
||||
type DropDownList interface {
|
||||
View
|
||||
getItems() []string
|
||||
}
|
||||
|
||||
type dropDownListData struct {
|
||||
viewData
|
||||
items []string
|
||||
dropDownListener []func(DropDownList, int)
|
||||
}
|
||||
|
||||
// NewDropDownList create new DropDownList object and return it
|
||||
func NewDropDownList(session Session, params Params) DropDownList {
|
||||
view := new(dropDownListData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newDropDownList(session Session) View {
|
||||
return NewDropDownList(session, nil)
|
||||
}
|
||||
|
||||
func (list *dropDownListData) Init(session Session) {
|
||||
list.viewData.Init(session)
|
||||
list.tag = "DropDownList"
|
||||
list.items = []string{}
|
||||
list.dropDownListener = []func(DropDownList, int){}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) Remove(tag string) {
|
||||
list.remove(strings.ToLower(tag))
|
||||
}
|
||||
|
||||
func (list *dropDownListData) remove(tag string) {
|
||||
switch tag {
|
||||
case Items:
|
||||
if len(list.items) > 0 {
|
||||
list.items = []string{}
|
||||
updateInnerHTML(list.htmlID(), list.session)
|
||||
}
|
||||
|
||||
case Current:
|
||||
list.set(Current, 0)
|
||||
|
||||
case DropDownEvent:
|
||||
if len(list.dropDownListener) > 0 {
|
||||
list.dropDownListener = []func(DropDownList, int){}
|
||||
}
|
||||
|
||||
default:
|
||||
list.viewData.remove(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) Set(tag string, value interface{}) bool {
|
||||
return list.set(strings.ToLower(tag), value)
|
||||
}
|
||||
|
||||
func (list *dropDownListData) set(tag string, value interface{}) bool {
|
||||
switch tag {
|
||||
case Items:
|
||||
return list.setItems(value)
|
||||
|
||||
case Current:
|
||||
oldCurrent := GetDropDownCurrent(list, "")
|
||||
if !list.setIntProperty(Current, value) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !list.session.ignoreViewUpdates() {
|
||||
current := GetDropDownCurrent(list, "")
|
||||
if oldCurrent != current {
|
||||
list.session.runScript(fmt.Sprintf(`selectDropDownListItem('%s', %d)`, list.htmlID(), current))
|
||||
list.onSelectedItemChanged(current)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case DropDownEvent:
|
||||
return list.setDropDownListener(value)
|
||||
}
|
||||
|
||||
return list.viewData.set(tag, value)
|
||||
}
|
||||
|
||||
func (list *dropDownListData) setItems(value interface{}) bool {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
list.items = []string{value}
|
||||
|
||||
case []string:
|
||||
list.items = value
|
||||
|
||||
case []DataValue:
|
||||
list.items = []string{}
|
||||
for _, val := range value {
|
||||
if !val.IsObject() {
|
||||
list.items = append(list.items, val.Value())
|
||||
}
|
||||
}
|
||||
|
||||
case []fmt.Stringer:
|
||||
list.items = make([]string, len(value))
|
||||
for i, str := range value {
|
||||
list.items[i] = str.String()
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
items := []string{}
|
||||
for _, v := range value {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
items = append(items, val)
|
||||
|
||||
case fmt.Stringer:
|
||||
items = append(items, val.String())
|
||||
|
||||
case bool:
|
||||
if val {
|
||||
items = append(items, "true")
|
||||
} else {
|
||||
items = append(items, "false")
|
||||
}
|
||||
|
||||
case float32:
|
||||
items = append(items, fmt.Sprintf("%g", float64(val)))
|
||||
|
||||
case float64:
|
||||
items = append(items, fmt.Sprintf("%g", val))
|
||||
|
||||
case rune:
|
||||
items = append(items, string(val))
|
||||
|
||||
default:
|
||||
if n, ok := isInt(v); ok {
|
||||
items = append(items, strconv.Itoa(n))
|
||||
} else {
|
||||
notCompatibleType(Items, value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list.items = items
|
||||
|
||||
default:
|
||||
notCompatibleType(Items, value)
|
||||
return false
|
||||
}
|
||||
|
||||
if !list.session.ignoreViewUpdates() {
|
||||
updateInnerHTML(list.htmlID(), list.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (list *dropDownListData) setDropDownListener(value interface{}) bool {
|
||||
switch value := value.(type) {
|
||||
case func(DropDownList, int):
|
||||
list.dropDownListener = []func(DropDownList, int){value}
|
||||
return true
|
||||
|
||||
case func(int):
|
||||
list.dropDownListener = []func(DropDownList, int){func(list DropDownList, index int) {
|
||||
value(index)
|
||||
}}
|
||||
return true
|
||||
|
||||
case []func(DropDownList, int):
|
||||
list.dropDownListener = value
|
||||
return true
|
||||
|
||||
case []func(int):
|
||||
listeners := make([]func(DropDownList, int), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
notCompatibleType(DropDownEvent, value)
|
||||
return false
|
||||
}
|
||||
listeners[i] = func(list DropDownList, index int) {
|
||||
val(index)
|
||||
}
|
||||
}
|
||||
list.dropDownListener = listeners
|
||||
return true
|
||||
|
||||
case []interface{}:
|
||||
listeners := make([]func(DropDownList, int), len(value))
|
||||
for i, val := range value {
|
||||
if val == nil {
|
||||
notCompatibleType(DropDownEvent, value)
|
||||
return false
|
||||
}
|
||||
switch val := val.(type) {
|
||||
case func(DropDownList, int):
|
||||
listeners[i] = val
|
||||
|
||||
case func(int):
|
||||
listeners[i] = func(list DropDownList, index int) {
|
||||
val(index)
|
||||
}
|
||||
|
||||
default:
|
||||
notCompatibleType(DropDownEvent, value)
|
||||
return false
|
||||
}
|
||||
list.dropDownListener = listeners
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
notCompatibleType(DropDownEvent, value)
|
||||
return false
|
||||
}
|
||||
|
||||
func (list *dropDownListData) Get(tag string) interface{} {
|
||||
return list.get(strings.ToLower(tag))
|
||||
}
|
||||
|
||||
func (list *dropDownListData) get(tag string) interface{} {
|
||||
switch tag {
|
||||
case Items:
|
||||
return list.items
|
||||
|
||||
case Current:
|
||||
result, _ := intProperty(list, Current, list.session, 0)
|
||||
return result
|
||||
|
||||
case DropDownEvent:
|
||||
return list.dropDownListener
|
||||
}
|
||||
|
||||
return list.viewData.get(tag)
|
||||
}
|
||||
|
||||
func (list *dropDownListData) getItems() []string {
|
||||
return list.items
|
||||
}
|
||||
|
||||
func (list *dropDownListData) htmlTag() string {
|
||||
return "select"
|
||||
}
|
||||
|
||||
func (list *dropDownListData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
if list.items != nil {
|
||||
current := GetDropDownCurrent(list, "")
|
||||
notTranslate := GetNotTranslate(list, "")
|
||||
for i, item := range list.items {
|
||||
if i == current {
|
||||
buffer.WriteString("<option selected>")
|
||||
} else {
|
||||
buffer.WriteString("<option>")
|
||||
}
|
||||
if !notTranslate {
|
||||
item, _ = list.session.GetString(item)
|
||||
}
|
||||
|
||||
buffer.WriteString(item)
|
||||
buffer.WriteString("</option>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
list.viewData.htmlProperties(self, buffer)
|
||||
buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`)
|
||||
}
|
||||
|
||||
func (list *dropDownListData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
||||
list.viewData.htmlDisabledProperties(self, buffer)
|
||||
if IsDisabled(list) {
|
||||
buffer.WriteString(`disabled`)
|
||||
}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) onSelectedItemChanged(number int) {
|
||||
for _, listener := range list.dropDownListener {
|
||||
listener(list, number)
|
||||
}
|
||||
}
|
||||
|
||||
func (list *dropDownListData) handleCommand(self View, command string, data DataObject) bool {
|
||||
switch command {
|
||||
case "itemSelected":
|
||||
if text, ok := data.PropertyValue("number"); ok {
|
||||
if number, err := strconv.Atoi(text); err == nil {
|
||||
if GetDropDownCurrent(list, "") != number && number >= 0 && number < len(list.items) {
|
||||
list.properties[Current] = number
|
||||
list.onSelectedItemChanged(number)
|
||||
}
|
||||
} else {
|
||||
ErrorLog(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return list.viewData.handleCommand(self, command, data)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetDropDownListeners(view View) []func(DropDownList, int) {
|
||||
if value := view.Get(DropDownEvent); value != nil {
|
||||
if listeners, ok := value.([]func(DropDownList, int)); ok {
|
||||
return listeners
|
||||
}
|
||||
}
|
||||
return []func(DropDownList, int){}
|
||||
}
|
||||
|
||||
// func GetDropDownItems return the view items list
|
||||
func GetDropDownItems(view View, subviewID string) []string {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if list, ok := view.(DropDownList); ok {
|
||||
return list.getItems()
|
||||
}
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// func GetDropDownCurrentItem return the number of the selected item
|
||||
func GetDropDownCurrent(view View, subviewID string) int {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
result, _ := intProperty(view, Current, view.Session(), 0)
|
||||
return result
|
||||
}
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,632 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// EditTextChangedEvent is the constant for the "edit-text-changed" property tag.
|
||||
EditTextChangedEvent = "edit-text-changed"
|
||||
// EditViewType is the constant for the "edit-view-type" property tag.
|
||||
EditViewType = "edit-view-type"
|
||||
// EditViewPattern is the constant for the "edit-view-pattern" property tag.
|
||||
EditViewPattern = "edit-view-pattern"
|
||||
// Spellcheck is the constant for the "spellcheck" property tag.
|
||||
Spellcheck = "spellcheck"
|
||||
)
|
||||
|
||||
const (
|
||||
// SingleLineText - single-line text type of EditView
|
||||
SingleLineText = 0
|
||||
// PasswordText - password type of EditView
|
||||
PasswordText = 1
|
||||
// EmailText - e-mail type of EditView. Allows to enter one email
|
||||
EmailText = 2
|
||||
// EmailsText - e-mail type of EditView. Allows to enter multiple emails separeted by comma
|
||||
EmailsText = 3
|
||||
// URLText - url type of EditView. Allows to enter one url
|
||||
URLText = 4
|
||||
// PhoneText - telephone type of EditView. Allows to enter one phone number
|
||||
PhoneText = 5
|
||||
// MultiLineText - multi-line text type of EditView
|
||||
MultiLineText = 6
|
||||
)
|
||||
|
||||
// EditView - grid-container of View
|
||||
type EditView interface {
|
||||
View
|
||||
AppendText(text string)
|
||||
}
|
||||
|
||||
type editViewData struct {
|
||||
viewData
|
||||
textChangeListeners []func(EditView, string)
|
||||
}
|
||||
|
||||
// NewEditView create new EditView object and return it
|
||||
func NewEditView(session Session, params Params) EditView {
|
||||
view := new(editViewData)
|
||||
view.Init(session)
|
||||
setInitParams(view, params)
|
||||
return view
|
||||
}
|
||||
|
||||
func newEditView(session Session) View {
|
||||
return NewEditView(session, nil)
|
||||
}
|
||||
|
||||
func (edit *editViewData) Init(session Session) {
|
||||
edit.viewData.Init(session)
|
||||
edit.textChangeListeners = []func(EditView, string){}
|
||||
edit.tag = "EditView"
|
||||
}
|
||||
|
||||
func (edit *editViewData) normalizeTag(tag string) string {
|
||||
tag = strings.ToLower(tag)
|
||||
switch tag {
|
||||
case Type, "edit-type":
|
||||
return EditViewType
|
||||
|
||||
case Pattern, "edit-pattern":
|
||||
return EditViewPattern
|
||||
|
||||
case "maxlength", "maxlen":
|
||||
return MaxLength
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
func (edit *editViewData) Remove(tag string) {
|
||||
edit.remove(edit.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (edit *editViewData) remove(tag string) {
|
||||
if _, ok := edit.properties[tag]; ok {
|
||||
switch tag {
|
||||
case Hint:
|
||||
delete(edit.properties, Hint)
|
||||
removeProperty(edit.htmlID(), "placeholder", edit.session)
|
||||
|
||||
case MaxLength:
|
||||
delete(edit.properties, MaxLength)
|
||||
removeProperty(edit.htmlID(), "maxlength", edit.session)
|
||||
|
||||
case ReadOnly, Spellcheck:
|
||||
delete(edit.properties, tag)
|
||||
updateBoolProperty(edit.htmlID(), tag, false, edit.session)
|
||||
|
||||
case EditTextChangedEvent:
|
||||
if len(edit.textChangeListeners) > 0 {
|
||||
edit.textChangeListeners = []func(EditView, string){}
|
||||
}
|
||||
|
||||
case Text:
|
||||
oldText := GetText(edit, "")
|
||||
delete(edit.properties, tag)
|
||||
if oldText != "" {
|
||||
edit.textChanged("")
|
||||
edit.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, edit.htmlID(), ""))
|
||||
}
|
||||
|
||||
case EditViewPattern:
|
||||
oldText := GetEditViewPattern(edit, "")
|
||||
delete(edit.properties, tag)
|
||||
if oldText != "" {
|
||||
removeProperty(edit.htmlID(), Pattern, edit.session)
|
||||
}
|
||||
|
||||
case EditViewType:
|
||||
oldType := GetEditViewType(edit, "")
|
||||
delete(edit.properties, tag)
|
||||
if oldType != 0 {
|
||||
updateInnerHTML(edit.parentHTMLID(), edit.session)
|
||||
}
|
||||
|
||||
case Wrap:
|
||||
oldWrap := IsEditViewWrap(edit, "")
|
||||
delete(edit.properties, tag)
|
||||
if GetEditViewType(edit, "") == MultiLineText {
|
||||
if wrap := IsEditViewWrap(edit, ""); wrap != oldWrap {
|
||||
if wrap {
|
||||
updateProperty(edit.htmlID(), "wrap", "soft", edit.session)
|
||||
} else {
|
||||
updateProperty(edit.htmlID(), "wrap", "off", edit.session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
edit.viewData.remove(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (edit *editViewData) Set(tag string, value interface{}) bool {
|
||||
return edit.set(edit.normalizeTag(tag), value)
|
||||
}
|
||||
|
||||
func (edit *editViewData) set(tag string, value interface{}) bool {
|
||||
if value == nil {
|
||||
edit.remove(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case Text:
|
||||
oldText := GetText(edit, "")
|
||||
if text, ok := value.(string); ok {
|
||||
edit.properties[Text] = text
|
||||
if text = GetText(edit, ""); oldText != text {
|
||||
edit.textChanged(text)
|
||||
if GetEditViewType(edit, "") == MultiLineText {
|
||||
updateInnerHTML(edit.htmlID(), edit.Session())
|
||||
} else {
|
||||
text = strings.ReplaceAll(text, `"`, `\"`)
|
||||
text = strings.ReplaceAll(text, `'`, `\'`)
|
||||
text = strings.ReplaceAll(text, "\n", `\n`)
|
||||
text = strings.ReplaceAll(text, "\r", `\r`)
|
||||
edit.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, edit.htmlID(), text))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case Hint:
|
||||
oldText := GetHint(edit, "")
|
||||
if text, ok := value.(string); ok {
|
||||
edit.properties[Hint] = text
|
||||
if text = GetHint(edit, ""); oldText != text {
|
||||
if text != "" {
|
||||
updateProperty(edit.htmlID(), "placeholder", text, edit.session)
|
||||
} else {
|
||||
removeProperty(edit.htmlID(), "placeholder", edit.session)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case MaxLength:
|
||||
oldMaxLength := GetMaxLength(edit, "")
|
||||
if edit.setIntProperty(MaxLength, value) {
|
||||
if maxLength := GetMaxLength(edit, ""); maxLength != oldMaxLength {
|
||||
if maxLength > 0 {
|
||||
updateProperty(edit.htmlID(), "maxlength", strconv.Itoa(maxLength), edit.session)
|
||||
} else {
|
||||
removeProperty(edit.htmlID(), "maxlength", edit.session)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case ReadOnly:
|
||||
if edit.setBoolProperty(ReadOnly, value) {
|
||||
if IsReadOnly(edit, "") {
|
||||
updateProperty(edit.htmlID(), ReadOnly, "", edit.session)
|
||||
} else {
|
||||
removeProperty(edit.htmlID(), ReadOnly, edit.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case Spellcheck:
|
||||
if edit.setBoolProperty(Spellcheck, value) {
|
||||
updateBoolProperty(edit.htmlID(), Spellcheck, IsSpellcheck(edit, ""), edit.session)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case EditViewPattern:
|
||||
oldText := GetEditViewPattern(edit, "")
|
||||
if text, ok := value.(string); ok {
|
||||
edit.properties[Pattern] = text
|
||||
if text = GetEditViewPattern(edit, ""); oldText != text {
|
||||
if text != "" {
|
||||
updateProperty(edit.htmlID(), Pattern, text, edit.session)
|
||||
} else {
|
||||
removeProperty(edit.htmlID(), Pattern, edit.session)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case EditViewType:
|
||||
oldType := GetEditViewType(edit, "")
|
||||
if edit.setEnumProperty(EditViewType, value, enumProperties[EditViewType].values) {
|
||||
if GetEditViewType(edit, "") != oldType {
|
||||
updateInnerHTML(edit.parentHTMLID(), edit.session)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case Wrap:
|
||||
oldWrap := IsEditViewWrap(edit, "")
|
||||
if edit.setBoolProperty(Wrap, value) {
|
||||
if GetEditViewType(edit, "") == MultiLineText {
|
||||
if wrap := IsEditViewWrap(edit, ""); wrap != oldWrap {
|
||||
if wrap {
|
||||
updateProperty(edit.htmlID(), "wrap", "soft", edit.session)
|
||||
} else {
|
||||
updateProperty(edit.htmlID(), "wrap", "off", edit.session)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case EditTextChangedEvent:
|
||||
ok := edit.setChangeListeners(value)
|
||||
if !ok {
|
||||
notCompatibleType(tag, value)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
return edit.viewData.set(tag, value)
|
||||
}
|
||||
|
||||
func (edit *editViewData) setChangeListeners(value interface{}) bool {
|
||||
switch value := value.(type) {
|
||||
case func(EditView, string):
|
||||
edit.textChangeListeners = []func(EditView, string){value}
|
||||
|
||||
case func(string):
|
||||
fn := func(view EditView, text string) {
|
||||
value(text)
|
||||
}
|
||||
edit.textChangeListeners = []func(EditView, string){fn}
|
||||
|
||||
case []func(EditView, string):
|
||||
edit.textChangeListeners = value
|
||||
|
||||
case []func(string):
|
||||
listeners := make([]func(EditView, string), len(value))
|
||||
for i, v := range value {
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
listeners[i] = func(view EditView, text string) {
|
||||
v(text)
|
||||
}
|
||||
}
|
||||
edit.textChangeListeners = listeners
|
||||
|
||||
case []interface{}:
|
||||
listeners := make([]func(EditView, string), len(value))
|
||||
for i, v := range value {
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case func(EditView, string):
|
||||
listeners[i] = v
|
||||
|
||||
case func(string):
|
||||
listeners[i] = func(view EditView, text string) {
|
||||
v(text)
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
edit.textChangeListeners = listeners
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (edit *editViewData) Get(tag string) interface{} {
|
||||
return edit.get(edit.normalizeTag(tag))
|
||||
}
|
||||
|
||||
func (edit *editViewData) get(tag string) interface{} {
|
||||
return edit.viewData.get(tag)
|
||||
}
|
||||
|
||||
func (edit *editViewData) AppendText(text string) {
|
||||
if GetEditViewType(edit, "") == MultiLineText {
|
||||
if value := edit.getRaw(Text); value != nil {
|
||||
if textValue, ok := value.(string); ok {
|
||||
textValue += text
|
||||
edit.properties[Text] = textValue
|
||||
|
||||
text := strings.ReplaceAll(text, `"`, `\"`)
|
||||
text = strings.ReplaceAll(text, `'`, `\'`)
|
||||
text = strings.ReplaceAll(text, "\n", `\n`)
|
||||
text = strings.ReplaceAll(text, "\r", `\r`)
|
||||
|
||||
edit.session.runScript(`appendToInnerHTML("` + edit.htmlID() + `", "` + text + `")`)
|
||||
|
||||
edit.textChanged(textValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
edit.set(Text, text)
|
||||
} else {
|
||||
edit.set(Text, GetText(edit, "")+text)
|
||||
}
|
||||
}
|
||||
|
||||
func (edit *editViewData) textChanged(newText string) {
|
||||
for _, listener := range edit.textChangeListeners {
|
||||
listener(edit, newText)
|
||||
}
|
||||
}
|
||||
|
||||
func (edit *editViewData) htmlTag() string {
|
||||
if GetEditViewType(edit, "") == MultiLineText {
|
||||
return "textarea"
|
||||
}
|
||||
return "input"
|
||||
}
|
||||
|
||||
func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||
edit.viewData.htmlProperties(self, buffer)
|
||||
|
||||
writeSpellcheck := func() {
|
||||
if spellcheck := IsSpellcheck(edit, ""); spellcheck {
|
||||
buffer.WriteString(` spellcheck="true"`)
|
||||
} else {
|
||||
buffer.WriteString(` spellcheck="false"`)
|
||||
}
|
||||
}
|
||||
|
||||
editType := GetEditViewType(edit, "")
|
||||
switch editType {
|
||||
case SingleLineText:
|
||||
buffer.WriteString(` type="text" inputmode="text"`)
|
||||
writeSpellcheck()
|
||||
|
||||
case PasswordText:
|
||||
buffer.WriteString(` type="password" inputmode="text"`)
|
||||
|
||||
case EmailText:
|
||||
buffer.WriteString(` type="email" inputmode="email"`)
|
||||
|
||||
case EmailsText:
|
||||
buffer.WriteString(` type="email" inputmode="email" multiple`)
|
||||
|
||||
case URLText:
|
||||
buffer.WriteString(` type="url" inputmode="url"`)
|
||||
|
||||
case PhoneText:
|
||||
buffer.WriteString(` type="tel" inputmode="tel"`)
|
||||
|
||||
case MultiLineText:
|
||||
if IsEditViewWrap(edit, "") {
|
||||
buffer.WriteString(` wrap="soft"`)
|
||||
} else {
|
||||
buffer.WriteString(` wrap="off"`)
|
||||
}
|
||||
writeSpellcheck()
|
||||
}
|
||||
|
||||
if IsReadOnly(edit, "") {
|
||||
buffer.WriteString(` readonly`)
|
||||
}
|
||||
|
||||
if maxLength := GetMaxLength(edit, ""); maxLength > 0 {
|
||||
buffer.WriteString(` maxlength="`)
|
||||
buffer.WriteString(strconv.Itoa(maxLength))
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
|
||||
if hint := GetHint(edit, ""); hint != "" {
|
||||
buffer.WriteString(` placeholder="`)
|
||||
buffer.WriteString(hint)
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
|
||||
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
||||
if pattern := GetEditViewPattern(edit, ""); pattern != "" {
|
||||
buffer.WriteString(` pattern="`)
|
||||
buffer.WriteString(pattern)
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
|
||||
if editType != MultiLineText {
|
||||
if text := GetText(edit, ""); text != "" {
|
||||
buffer.WriteString(` value="`)
|
||||
buffer.WriteString(text)
|
||||
buffer.WriteByte('"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (edit *editViewData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
||||
if IsDisabled(self) {
|
||||
buffer.WriteString(` disabled`)
|
||||
}
|
||||
edit.viewData.htmlDisabledProperties(self, buffer)
|
||||
}
|
||||
|
||||
func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||
if GetEditViewType(edit, "") == MultiLineText {
|
||||
text := strings.ReplaceAll(GetText(edit, ""), `"`, `\"`)
|
||||
text = strings.ReplaceAll(text, "\n", `\n`)
|
||||
text = strings.ReplaceAll(text, "\r", `\r`)
|
||||
buffer.WriteString(strings.ReplaceAll(text, `'`, `\'`))
|
||||
}
|
||||
}
|
||||
|
||||
func (edit *editViewData) handleCommand(self View, command string, data DataObject) bool {
|
||||
switch command {
|
||||
case "textChanged":
|
||||
oldText := GetText(edit, "")
|
||||
if text, ok := data.PropertyValue("text"); ok {
|
||||
edit.properties[Text] = text
|
||||
if text := GetText(edit, ""); text != oldText {
|
||||
edit.textChanged(text)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return edit.viewData.handleCommand(self, command, data)
|
||||
}
|
||||
|
||||
// GetText returns a text of the subview.
|
||||
// If the second argument (subviewID) is "" then a text of the first argument (view) is returned.
|
||||
func GetText(view View, subviewID string) string {
|
||||
if subviewID != "" {
|
||||
view = ViewByID(view, subviewID)
|
||||
}
|
||||
if view != nil {
|
||||
if text, ok := stringProperty(view, Text, view.Session()); ok {
|
||||
return text
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHint returns a hint text of the subview.
|
||||
// If the second argument (subviewID) is "" then a text of the first argument (view) is returned.
|
||||
func GetHint(view View, subviewID string) string {
|
||||
if subviewID |