github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/framework/style-editor/styled.go (about) 1 package styled 2 3 import ( 4 "bytes" 5 "fmt" 6 "image" 7 "image/color" 8 "image/draw" 9 "io" 10 "math" 11 "reflect" 12 13 "github.com/gop9/olt/framework" 14 lbl "github.com/gop9/olt/framework/label" 15 "github.com/gop9/olt/framework/rect" 16 nstyle "github.com/gop9/olt/framework/style" 17 "github.com/gop9/olt/gio/io/pointer" 18 ) 19 20 type styleEditor struct { 21 style *nstyle.Style 22 saveFn func(string) 23 24 colorPickers map[*color.RGBA]*colorPicker 25 } 26 27 func EditStyle(w framework.MasterWindow, flags framework.WindowFlags, saveFn func(string)) { 28 w.PopupOpen("Style Editor", framework.WindowTitle|framework.WindowBorder|framework.WindowMovable|framework.WindowScalable|flags, rect.Rect{0, 0, 400, 600}, true, StyleEditor(w.Style(), saveFn)) 29 } 30 31 func StyleEditor(style *nstyle.Style, saveFn func(string)) framework.UpdateFn { 32 se := &styleEditor{style: style, saveFn: saveFn} 33 return se.Update 34 } 35 36 func (se *styleEditor) Update(w *framework.Window) { 37 if se.style == nil { 38 se.style = w.Master().Style() 39 } 40 val := reflect.ValueOf(se.style.Unscaled()).Elem() 41 w.MenubarBegin() 42 w.Row(25).Static(0, 100) 43 w.Spacing(1) 44 if w.ButtonText("Save") && se.saveFn != nil { 45 var buf bytes.Buffer 46 se.serialize(&buf, "style", val) 47 se.saveFn(buf.String()) 48 } 49 w.MenubarEnd() 50 if se.editStruct(w, val) { 51 se.style.Scale(se.style.Scaling) 52 } 53 } 54 55 func (se *styleEditor) editStruct(w *framework.Window, val reflect.Value) bool { 56 changed := false 57 for i := 0; i < val.NumField(); i++ { 58 field := val.Type().Field(i) 59 if field.PkgPath != "" { 60 continue 61 } 62 fieldval := val.Field(i) 63 fieldiface := fieldval.Addr().Interface() 64 switch fieldptr := fieldiface.(type) { 65 case *nstyle.Text, *nstyle.Button, *nstyle.Toggle, *nstyle.Selectable, *nstyle.Slider, *nstyle.Progress, *nstyle.Property, *nstyle.Edit, *nstyle.Scrollbar, *nstyle.Tab, *nstyle.Combo, *nstyle.Window, *nstyle.WindowHeader: 66 if w.TreePush(framework.TreeTab, field.Name, false) { 67 if se.editStruct(w, fieldval) { 68 changed = true 69 } 70 w.TreePop() 71 } 72 case *color.RGBA: 73 if se.editColor(w, field.Name, fieldptr) { 74 changed = true 75 } 76 case *int: 77 w.Row(20).Dynamic(1) 78 if w.PropertyInt(field.Name, -10000, fieldptr, 10000, 1, 1) { 79 changed = true 80 } 81 case *uint16: 82 w.Row(20).Dynamic(1) 83 v := int(*fieldptr) 84 if w.PropertyInt(field.Name, -10000, &v, 10000, 1, 1) { 85 *fieldptr = uint16(v) 86 changed = true 87 } 88 case *uint32: 89 w.Row(20).Dynamic(1) 90 v := int(*fieldptr) 91 if w.PropertyInt(field.Name, -10000, &v, 10000, 1, 1) { 92 *fieldptr = uint32(v) 93 changed = true 94 } 95 case *lbl.SymbolType: 96 w.Row(20).Dynamic(1) 97 v := int(*fieldptr) 98 if w.PropertyInt(field.Name, -10000, &v, 10000, 1, 1) { 99 *fieldptr = lbl.SymbolType(v) 100 changed = true 101 } 102 case *nstyle.HeaderAlign: 103 w.Row(20).Dynamic(1) 104 v := int(*fieldptr) 105 if w.PropertyInt(field.Name, -10000, &v, 10000, 1, 1) { 106 *fieldptr = nstyle.HeaderAlign(v) 107 changed = true 108 } 109 case *image.Point: 110 w.Row(20).Static(200, 0) 111 w.Label(field.Name, "LC") 112 if w.PropertyInt("X:", -10000, &fieldptr.X, 10000, 1, 1) { 113 changed = true 114 } 115 w.Spacing(1) 116 if w.PropertyInt("Y:", -10000, &fieldptr.Y, 10000, 1, 1) { 117 changed = true 118 } 119 case *nstyle.Item: 120 if fieldptr.Type == nstyle.ItemColor { 121 if se.editColor(w, field.Name, &fieldptr.Data.Color) { 122 changed = true 123 } 124 } 125 } 126 } 127 128 return changed 129 } 130 131 func (se *styleEditor) serialize(wr io.Writer, prefix string, val reflect.Value) { 132 for i := 0; i < val.NumField(); i++ { 133 field := val.Type().Field(i) 134 if field.PkgPath != "" { 135 continue 136 } 137 fieldval := val.Field(i) 138 fieldiface := fieldval.Addr().Interface() 139 switch fieldptr := fieldiface.(type) { 140 case *nstyle.Text, *nstyle.Button, *nstyle.Toggle, *nstyle.Selectable, *nstyle.Slider, *nstyle.Progress, *nstyle.Property, *nstyle.Edit, *nstyle.Scrollbar, *nstyle.Tab, *nstyle.Combo, *nstyle.Window, *nstyle.WindowHeader: 141 se.serialize(wr, fmt.Sprintf("%s.%s", prefix, field.Name), fieldval) 142 case *color.RGBA: 143 fmt.Fprintf(wr, "%s.%s = color.RGBA{ %d, %d, %d, %d }\n", prefix, field.Name, fieldptr.R, fieldptr.G, fieldptr.B, fieldptr.A) 144 145 case *int: 146 fmt.Fprintf(wr, "%s.%s = %d\n", prefix, field.Name, *fieldptr) 147 case *uint16: 148 fmt.Fprintf(wr, "%s.%s = %d\n", prefix, field.Name, *fieldptr) 149 case *uint32: 150 fmt.Fprintf(wr, "%s.%s = %d\n", prefix, field.Name, *fieldptr) 151 case *bool: 152 fmt.Fprintf(wr, "%s.%s = %v\n", prefix, field.Name, *fieldptr) 153 case *lbl.SymbolType: 154 fmt.Fprintf(wr, "%s.%s = lbl.SymbolType(%d)\n", prefix, field.Name, *fieldptr) 155 case *nstyle.HeaderAlign: 156 fmt.Fprintf(wr, "%s.%s = nstyle.HeaderAlign(%d)\n", prefix, field.Name, *fieldptr) 157 case *image.Point: 158 fmt.Fprintf(wr, "%s.%s = image.Point{ %d, %d }\n", prefix, field.Name, fieldptr.X, fieldptr.Y) 159 case *nstyle.Item: 160 c := fieldptr.Data.Color 161 fmt.Fprintf(wr, "%s.%s.Type = nstyle.ItemColor\n", prefix, field.Name) 162 fmt.Fprintf(wr, "%s.%s.Data.Color = color.RGBA{ %d, %d, %d, %d }\n", prefix, field.Name, c.R, c.G, c.B, c.A) 163 default: 164 switch field.Name { 165 case "DrawBegin", "DrawEnd", "Draw": 166 default: 167 fmt.Fprintf(wr, "// not serializing %s.%s of type %T\n", prefix, field.Name, fieldiface) 168 } 169 } 170 } 171 } 172 173 func (se *styleEditor) editColor(w *framework.Window, name string, pc *color.RGBA) bool { 174 if se.colorPickers == nil { 175 se.colorPickers = make(map[*color.RGBA]*colorPicker) 176 } 177 cp := se.colorPickers[pc] 178 if cp == nil { 179 cp = newColorPicker(pc) 180 se.colorPickers[pc] = cp 181 } 182 183 w.Row(20).Dynamic(3) 184 w.Label(name, "LC") 185 changed := false 186 if cp.Edit(w) { 187 changed = true 188 } 189 if cp.Picker(w) { 190 changed = true 191 } 192 return changed 193 } 194 195 type colorPicker struct { 196 h, s, v int 197 r, g, b uint8 198 a int 199 200 ed framework.TextEditor 201 202 dst *color.RGBA 203 204 hslider, sslider, vslider [256]color.RGBA 205 himg, simg, vimg *image.RGBA 206 } 207 208 func newColorPicker(pc *color.RGBA) *colorPicker { 209 cp := &colorPicker{} 210 cp.dst = pc 211 212 cp.setRGB() 213 214 cp.ed.Flags = framework.EditField | framework.EditSigEnter | framework.EditSelectable | framework.EditClipboard 215 cp.setBuffer() 216 217 return cp 218 } 219 220 func (cp *colorPicker) setBuffer() { 221 cp.ed.Buffer = []rune(fmt.Sprintf("%02x%02x%02x%02x", cp.dst.R, cp.dst.G, cp.dst.B, cp.dst.A)) 222 } 223 224 func (cp *colorPicker) setRGB() { 225 r, g, b, a := cp.dst.RGBA() 226 227 cp.r = uint8(float64(r) / float64(a) * 255) 228 cp.g = uint8(float64(g) / float64(a) * 255) 229 cp.b = uint8(float64(b) / float64(a) * 255) 230 cp.a = int(a >> 8) 231 232 cp.h, cp.s, cp.v = rgb2hsv(cp.r, cp.g, cp.b) 233 cp.recalcSliders() 234 } 235 236 func (cp *colorPicker) Edit(w *framework.Window) bool { 237 a := cp.ed.Edit(w) 238 if a&framework.EditCommitted != 0 { 239 fmt.Sscanf(string(cp.ed.Buffer), "%2x%2x%2x%2x", &cp.dst.R, &cp.dst.G, &cp.dst.B, &cp.dst.A) 240 cp.setRGB() 241 return true 242 } 243 return false 244 } 245 246 func (cp *colorPicker) Picker(w *framework.Window) bool { 247 if w := w.Combo(lbl.C(color.RGBA{cp.r, cp.g, cp.b, 0xff}), 1000, nil); w != nil { 248 w.Row(20).Static(20, 0) 249 w.Label("H:", "LC") 250 changed := false 251 if colorProgress(w, &cp.h, &cp.hslider, &cp.himg) { 252 changed = true 253 } 254 w.Label("S:", "LC") 255 if colorProgress(w, &cp.s, &cp.sslider, &cp.simg) { 256 changed = true 257 } 258 w.Label("V:", "LC") 259 if colorProgress(w, &cp.v, &cp.vslider, &cp.vimg) { 260 changed = true 261 } 262 w.Label("A:", "LC") 263 if w.SliderInt(0, &cp.a, 255, 1) { 264 changed = true 265 } 266 267 if changed { 268 cp.r, cp.g, cp.b = hsv2rgb(cp.h, cp.s, cp.v) 269 270 n := color.NRGBA{cp.r, cp.g, cp.b, uint8(cp.a)} 271 sr, sg, sb, sa := n.RGBA() 272 273 cp.dst.R = uint8(sr >> 8) 274 cp.dst.G = uint8(sg >> 8) 275 cp.dst.B = uint8(sb >> 8) 276 cp.dst.A = uint8(sa >> 8) 277 278 cp.setBuffer() 279 cp.recalcSliders() 280 return true 281 } 282 } 283 return false 284 } 285 286 func (cp *colorPicker) recalcSliders() { 287 makeSlider(&cp.hslider, func(h int) (r, g, b uint8) { 288 return hsv2rgb(h, cp.s, cp.v) 289 }) 290 makeSlider(&cp.sslider, func(s int) (r, g, b uint8) { 291 return hsv2rgb(cp.h, s, cp.v) 292 }) 293 makeSlider(&cp.vslider, func(v int) (r, g, b uint8) { 294 return hsv2rgb(cp.h, cp.s, v) 295 }) 296 } 297 298 func makeSlider(slider *[256]color.RGBA, fn func(x int) (r, g, b uint8)) { 299 for x := 0; x < 256; x++ { 300 r, g, b := fn(x) 301 slider[x] = color.RGBA{r, g, b, 0xff} 302 } 303 } 304 305 func colorProgress(w *framework.Window, x *int, slider *[256]color.RGBA, pimg **image.RGBA) bool { 306 const maxval = 255 307 308 state := w.CustomState() 309 310 if state == nstyle.WidgetStateActive { 311 if !w.Input().Mouse.Down(pointer.ButtonLeft) { 312 state = nstyle.WidgetStateInactive 313 } 314 } 315 316 bounds, out := w.Custom(state) 317 if out == nil { 318 return false 319 } 320 321 value := *x 322 if state == nstyle.WidgetStateActive { 323 ratio := float64(w.Input().Mouse.Pos.X-bounds.X) / float64(bounds.W) 324 if ratio < 0 { 325 ratio = 0 326 } 327 value = int(maxval * ratio) 328 if value < 0 { 329 value = 0 330 } 331 if value > maxval { 332 value = maxval 333 } 334 } 335 336 style := w.Master().Style() 337 //bg := &style.Progress.Normal.Data.Color 338 cursor := style.Checkbox.CursorNormal.Data.Color 339 340 var r image.Rectangle 341 { 342 zbounds := bounds 343 zbounds.X = 0 344 zbounds.Y = 0 345 r = zbounds.Rectangle() 346 } 347 348 if *pimg == nil { 349 *pimg = image.NewRGBA(r) 350 } 351 352 if (*pimg).Bounds() != r { 353 *pimg = image.NewRGBA(r) 354 } 355 356 r = (*pimg).Bounds() 357 358 for x := r.Min.X; x < r.Max.X; x++ { 359 c := slider[int(float64(x-r.Min.X)/float64(r.Max.X-r.Min.X)*maxval)] 360 col := image.Rect(x, r.Min.X, x+1, r.Max.Y) 361 col = col.Intersect(r) 362 draw.Draw(*pimg, col, image.NewUniform(c), image.Point{}, draw.Src) 363 } 364 365 out.DrawImage(bounds, *pimg) 366 367 cursorRect := bounds 368 cursorRect.W = cursorRect.H 369 cursorRect.X = int((float64(value)/255)*float64(bounds.W)) + bounds.X - cursorRect.W/2 370 371 oldclip := out.Clip 372 out.PushScissor(bounds) 373 out.FillCircle(cursorRect, cursor) 374 out.PushScissor(oldclip) 375 376 if value != *x { 377 *x = value 378 return true 379 } 380 381 return false 382 } 383 384 func hsv2rgb(inh, ins, inv int) (outr, outg, outb uint8) { 385 h, s, v := float64(inh)/255, float64(ins)/255, float64(inv)/255 386 i := math.Floor(float64(h) * 6) 387 f := float64(h)*6 - i 388 p := float64(v) * (1 - float64(s)) 389 q := float64(v) * (1 - f*float64(s)) 390 t := float64(v) * (1 - (1-f)*float64(s)) 391 392 var r, g, b float64 393 394 switch int(i) % 6 { 395 case 0: 396 r = float64(v) 397 g = t 398 b = p 399 case 1: 400 r = q 401 g = float64(v) 402 b = p 403 case 2: 404 r = p 405 g = float64(v) 406 b = t 407 case 3: 408 r = p 409 g = q 410 b = float64(v) 411 case 4: 412 r = t 413 g = p 414 b = float64(v) 415 case 5: 416 r = float64(v) 417 g = p 418 b = q 419 } 420 421 return uint8(r * 255), uint8(g * 255), uint8(b * 255) 422 } 423 424 func rgb2hsv(inr, ing, inb uint8) (int, int, int) { 425 r := float64(inr) / 255 426 g := float64(ing) / 255 427 b := float64(inb) / 255 428 429 max := math.Max(math.Max(r, g), b) 430 min := math.Min(math.Min(r, g), b) 431 var h, s float64 432 v := max 433 434 var d = max - min 435 if max == 0 { 436 s = 0 437 } else { 438 s = d / max 439 } 440 441 if max == min { 442 h = 0 // achromatic 443 } else { 444 switch max { 445 case r: 446 coeff := float64(0) 447 if g < b { 448 coeff = 6 449 } 450 h = (g-b)/d + coeff 451 case g: 452 h = (b-r)/d + 2 453 case b: 454 h = (r-g)/d + 4 455 } 456 457 h /= 6 458 } 459 460 return int(h * 255), int(s * 255), int(v * 255) 461 }