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  }