@@ -1028,9 +1028,87 @@ func (ui *AppUI) layoutApp(gtx layout.Context) layout.Dimensions {
10281028 ui .layoutVarPopup (gtx )
10291029 }
10301030
1031+ // Settings color picker — rendered as a deferred overlay on top of
1032+ // everything so it floats over the rest of the settings panel
1033+ // (matches the method dropdown's behaviour). The picker's own
1034+ // gestures handle drag input within the deferred area.
1035+ if ui .SettingsOpen && ui .SettingsState != nil && ui .SettingsState .ColorPicker .isOpen () {
1036+ ui .layoutColorPickerOverlay (gtx )
1037+ }
1038+
10311039 return dim
10321040}
10331041
1042+ // layoutColorPickerOverlay renders the inline HSV picker as a deferred
1043+ // overlay anchored near where the user clicked the swatch. Uses
1044+ // op.Defer so the picker draws on top of all preceding ops in the
1045+ // current frame. A full-window backdrop registered inside the same
1046+ // macro detects Press events outside the picker rect and dismisses it
1047+ // — same click-outside-to-close pattern the var popup uses.
1048+ func (ui * AppUI ) layoutColorPickerOverlay (gtx layout.Context ) {
1049+ p := & ui .SettingsState .ColorPicker
1050+ pickerW := gtx .Dp (unit .Dp (240 ))
1051+ pickerH := gtx .Dp (unit .Dp (216 ))
1052+ gap := gtx .Dp (unit .Dp (6 ))
1053+
1054+ // Anchor below the cursor, flipped above if there's no room. Then
1055+ // clamp to keep the popup fully on-screen.
1056+ px := int (p .anchor .X ) + gap
1057+ py := int (p .anchor .Y ) + gap
1058+ if px + pickerW > gtx .Constraints .Max .X {
1059+ px = gtx .Constraints .Max .X - pickerW - gap
1060+ }
1061+ if py + pickerH > gtx .Constraints .Max .Y {
1062+ py = int (p .anchor .Y ) - pickerH - gap
1063+ }
1064+ if px < 0 {
1065+ px = 0
1066+ }
1067+ if py < 0 {
1068+ py = 0
1069+ }
1070+ pickerRect := image .Rect (px , py , px + pickerW , py + pickerH )
1071+
1072+ macro := op .Record (gtx .Ops )
1073+
1074+ // Backdrop covers the whole window. event.Op registers a tag whose
1075+ // pointer.Filter catches every Press; presses inside pickerRect are
1076+ // the user driving the picker (drag SV, hue, click Close), so we
1077+ // ignore them — anything else means "click outside" and closes the
1078+ // picker. Backdrop is drawn before the picker so the picker still
1079+ // renders on top within the same op.Defer.
1080+ backdropStack := clip.Rect {Max : gtx .Constraints .Max }.Push (gtx .Ops )
1081+ for {
1082+ ev , ok := gtx .Event (pointer.Filter {
1083+ Target : & p .backdrop ,
1084+ Kinds : pointer .Press ,
1085+ })
1086+ if ! ok {
1087+ break
1088+ }
1089+ pe , ok := ev .(pointer.Event )
1090+ if ! ok {
1091+ continue
1092+ }
1093+ pos := image .Pt (int (pe .Position .X ), int (pe .Position .Y ))
1094+ if pos .In (pickerRect ) {
1095+ continue
1096+ }
1097+ p .closePicker ()
1098+ }
1099+ event .Op (gtx .Ops , & p .backdrop )
1100+ backdropStack .Pop ()
1101+
1102+ // Picker on top of the backdrop.
1103+ pickerOff := op .Offset (image .Pt (px , py )).Push (gtx .Ops )
1104+ pickerGtx := gtx
1105+ pickerGtx .Constraints .Min = image .Pt (pickerW , pickerH )
1106+ pickerGtx .Constraints .Max = pickerGtx .Constraints .Min
1107+ renderColorPicker (pickerGtx , ui .Theme , p )
1108+ pickerOff .Pop ()
1109+ op .Defer (gtx .Ops , macro .Stop ())
1110+ }
1111+
10341112func (ui * AppUI ) layoutVarPopup (gtx layout.Context ) {
10351113 popupW := gtx .Dp (unit .Dp (360 ))
10361114 popupH := gtx .Dp (unit .Dp (180 ))
0 commit comments