diff --git a/CHANGELOG.md b/CHANGELOG.md index f0470ae..80b7b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,17 @@ because it turns other people's test suites red. ### Changed +- **The window is painted from one system rather than from colours chosen one + at a time.** Every surface and every piece of text now comes from a single + ladder: one hue, an even step between one surface and the next, and one + lightness shared by the accent and the three colours that carry meaning. + What you see is a window that reads the same way it did, with the things + that were nearly the same colour no longer nearly the same colour - the list + that drops down from the format box stands clear of the form behind it, + where it used to sit almost exactly as light as the box it came from. No + generated file changes in any way. The old values met every contrast + threshold and the new ones meet the same thresholds, checked the same way. + - **The window opens on a preset that was chosen rather than sorted.** The Presets tab, and the "Build on a preset" section of the batches screen, used to start on whichever preset came first alphabetically - so what you @@ -195,6 +206,15 @@ because it turns other people's test suites red. ### Added +- **The palette is something you can open.** `tfg --catalogue`, the hidden + screen that shows every control the window is built from, now ends with the + palette itself: every colour, what it is for, the value it holds and the + measurement that decides whether it is doing its job - the contrast ratio + for anything read, the distance in lightness for a surface. The light + palette is shown beside it and labelled: it is worked out and held to the + same thresholds, and the window does not use it, because this program has + one look. + - **Two more formats, both for configuration: `yaml` and `toml`.** A config file at an exact size is what a size-limited upload is for a picture, and neither format had a way to ask for one. Run `tfg formats` for all twenty diff --git a/internal/guard/palettepage_test.go b/internal/guard/palettepage_test.go new file mode 100644 index 0000000..7d46bc2 --- /dev/null +++ b/internal/guard/palettepage_test.go @@ -0,0 +1,167 @@ +package guard + +import ( + "fmt" + "image/color" + "strings" + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + + "github.com/donislawdev/TestingFilesGenerator/internal/gui/catalogue" + "github.com/donislawdev/TestingFilesGenerator/internal/gui/parts" +) + +// The palette page shows every colour the palette has, and says how to read +// each one. +// +// The page builds its rows from parts.PaletteNames rather than from a list +// beside it, so a colour cannot be missing from the page - it can only arrive +// with nothing said about it, which is what this guard is really about. A row +// that says "no reading written for this one yet" is a colour somebody added +// without deciding what it is measured against, and against WHAT is the half +// of a palette that gets forgotten: every threshold in this project is a pair. +// +// Both variants are asked about. The light one is computed and not installed, +// and a page that quietly showed only the installed half would leave that work +// exactly as invisible as it was before this page existed. +// +// What this guard does NOT cover, named because a control that is quiet +// about its limits is worse than no control: it asks whether a measurement is +// THERE, never whether it was taken against the right surface. Proved by +// mutation on 2026-09-23 - putting the hint's ratio back against a panel, +// which is the very mistake the review of #124 found, leaves this green. That +// question needs to know where each ink is drawn, which is a fact about the +// window rather than about this page, and it is asked in +// TestEachSurfaceIsToldFromTheOneUnderIt, where the pairs are named. +func TestThePalettePageShowsEveryColourAndHowToReadIt(t *testing.T) { + shown := textIn(catalogue.Page()) + + names := parts.PaletteNames() + if len(names) < 20 { + t.Fatalf("the palette answers for %d names, too few to be the real palette", len(names)) + } + + // Line by line rather than over the whole page, and that correction came + // from the review of #124. Asking whether a name and a value appear + // SOMEWHERE on the page is a question a broken page answers just as well: + // proved by mutation on 2026-09-23, deleting the purpose from every row + // and then deleting the measurement from every row both left this guard + // green, because the names and the values were still there. A row is two + // lines - "name - what it is for" and "value - what it measures" - and + // both halves are what the page is for. + lines := strings.Split(shown, "\n") + lineWith := func(prefix string) (string, bool) { + for _, line := range lines { + if strings.HasPrefix(line, prefix) { + return strings.TrimPrefix(line, prefix), true + } + } + return "", false + } + + for _, name := range names { + if !strings.Contains(shown, string(name)) { + t.Errorf("the palette page never names %s", name) + } + + // What the colour is for, on the row that names it. + if what, found := lineWith(string(name) + " - "); !found || len(strings.TrimSpace(what)) < 10 { + t.Errorf("the palette page shows %s with nothing saying what it is for - "+ + "a swatch and a name is a colour chart, and the reason this page exists "+ + "is the sentence beside it", name) + } + if !parts.PaletteRanked(name) { + t.Errorf("%s has no place in the order colours are read in, so it lands at the end of the page "+ + "where nobody decided it should be - give it a rank in parts/palette.go", name) + } + for _, variant := range []fyne.ThemeVariant{theme.VariantDark, theme.VariantLight} { + value := hexOf(parts.PaletteColour(name, variant)) + if !strings.Contains(shown, value) { + t.Errorf("the palette page never prints %s, which is what %s holds in the %s variant", + value, name, variantName(variant)) + continue + } + if measuredAgainstNothing[name] { + continue + } + // And the measurement, on the row that prints the value. Either a + // ratio or a distance in lightness, because those are the two + // units this palette is held to - a surface is asked for L* and + // anything read is asked for a ratio. + reading, found := lineWith(value + " - ") + if !found || (!strings.Contains(reading, ":1") && !strings.Contains(reading, "L*")) { + t.Errorf("the palette page prints %s for %s in the %s variant with no measurement beside it - "+ + "a value without the number that decides whether it works is the half of this page "+ + "that a document could have carried", value, name, variantName(variant)) + } + } + } + + // The two sentences that say a colour arrived without anybody saying how + // it is read. Asked about as text rather than by reaching into the + // catalogue's own table, because what matters is what a person opening + // the page is told. + for _, silence := range []string{"Not placed yet", "no reading written for this one yet"} { + if strings.Contains(shown, silence) { + t.Errorf("the palette page carries %q, so a colour is on it with nothing said about "+ + "what it is drawn on - the reading is the point of the page", silence) + } + } + + // And the light variant is actually there, under a heading that says what + // it is. It has never been on a screen and the whole reason for showing it + // is that "computed, not installed" is a state somebody has to be told. + for _, said := range []string{"Palette, as this window draws it", "not installed"} { + if !strings.Contains(shown, said) { + t.Errorf("the palette page never says %q", said) + } + } +} + +// hexOf is how the page writes a colour, which is the string this guard has to +// look for. Alpha and all: half the palette is translucent, and a page that +// printed those as if they were opaque would be describing colours nobody can +// edit. +// +// A SECOND formatter beside the catalogue's, on purpose, and not a thing to +// tidy away into one shared helper. Calling the page's own function here would +// ask the page whether it agrees with itself, and this project has a whole +// entry in CLAUDE.md about guards that compare a result with the same result: +// the site guard was green for a month while both halves said "twenty +// formats" at twenty-one. The source of truth is parts.PaletteColour, and the +// only way to ask whether the page prints THAT is to render the value here. +// If the two formats ever diverge this goes red, which is the correct outcome +// for a page changing how it writes a colour. +func hexOf(c color.Color) string { + r, g, b, a := c.RGBA() + switch { + case a == 0: + return "nothing - drawn transparent on purpose" + case a < 0xFFFF: + return fmt.Sprintf("#%02X%02X%02X at 0x%02X", r*0xFFFF/a>>8, g*0xFFFF/a>>8, b*0xFFFF/a>>8, a>>8) + } + return fmt.Sprintf("#%02X%02X%02X", r>>8, g>>8, b>>8) +} + +func variantName(v fyne.ThemeVariant) string { + if v == theme.VariantLight { + return "light" + } + return "dark" +} + +// The two colours that are measured against nothing, and why each is right to +// be. Written out rather than inferred, so that a THIRD colour losing its +// measurement is a red test rather than a quiet exemption. +// +// - The page is the floor of the ladder. Every surface above it is quoted as +// a distance from the one under it, and the floor has nothing under it. +// - The shadow is transparent on purpose since 2026-08-24 - it read as a +// hard band under the open format list - so there is no colour to measure. +// The page says that in words where a value would be. +var measuredAgainstNothing = map[fyne.ThemeColorName]bool{ + theme.ColorNameBackground: true, + theme.ColorNameShadow: true, +} diff --git a/internal/guard/paletterule_test.go b/internal/guard/paletterule_test.go new file mode 100644 index 0000000..853e849 --- /dev/null +++ b/internal/guard/paletterule_test.go @@ -0,0 +1,324 @@ +package guard + +import ( + "image/color" + "math" + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + + "github.com/donislawdev/TestingFilesGenerator/internal/gui/parts" +) + +// Every colour in the palette is the one the rule produces. +// +// The palette guard next door asks whether the values are READABLE. This one +// asks whether they are the values somebody would get by following the rule, +// and the two questions are not the same: a colour typed in by hand can pass +// every contrast threshold and still leave the window painted from two +// different systems. That is not hypothetical here. Measured on 2026-09-22, on +// the palette this one replaced: the surfaces stood at hue 291, the inks at +// 251 to 262, and the page and the main ink were pure neutral - three greys, +// every one of them above its threshold, none of them from the same idea. +// +// What this guard is really for is the edit nobody would call wrong. A value +// gets nudged for the screen it is being looked at on, the guards stay green +// because thresholds have room, and the ladder quietly stops being a ladder. +// It happened before this rule existed and it is written up beside the menu +// colour in parts/theme.go: a button's face was added at 31.7 L* while the +// menu - the surface that floats over everything - sat at 30.8, so the highest +// surface in the window was a button for a month. Nobody typed a wrong number. +// Two right numbers were chosen a month apart, against different neighbours, +// and nothing in the project compared them. +// +// The rule is stated here rather than in parts, and that is deliberate. The +// package keeps a flat table of colours a person can read and grep, and the +// arithmetic that produced it lives with the test that checks it - so the +// table is the artefact and this is the question asked of it. The same numbers +// drive tools/probes/palette/design.py, which is where a new palette is +// generated. +func TestThePaletteIsTheOneTheRuleProduces(t *testing.T) { + for _, variant := range []struct { + name string + v fyne.ThemeVariant + ladder map[fyne.ThemeColorName]float64 + accent float64 + sel float64 + }{ + {"dark", theme.VariantDark, darkLadder, 70.0, 30.5}, + {"light", theme.VariantLight, lightLadder, 42.0, 88.0}, + } { + // The neutrals: one hue, one curve, and lightness off the ladder. + // + // The question is asked of the BYTES rather than of the lightness, the + // chroma and the hue read back separately, and the first cut of this + // test got that wrong: it asked whether each colour still sat at hue + // 291 and went red on three light values sitting at 295 to 297. They + // were right. Eight bits per channel cannot hold a hue exactly at + // chroma under about four - half a step in a* or b* is several degrees + // there - so the tolerance would have had to grow with the tint until + // it measured nothing. Comparing what the rule produces, rounded the + // same way, has no such hole. + for name, want := range variant.ladder { + wanted := labToRGB(want, neutralChroma*math.Sin(math.Pi*want/100), neutralHue) + check(t, variant.name, name, parts.PaletteColour(name, variant.v), wanted, + "the ladder puts that rank at %.1f L* on hue %.0f", want, neutralHue) + } + + // The accent and the three colours that carry meaning: one lightness, + // and one SHARE of the chroma each hue can actually reach. A common + // chroma is not available at one lightness - at 70 L* red reaches 49.5 + // where green reaches 80.1 - so what is shared is the share. + for name, hue := range map[fyne.ThemeColorName]float64{ + theme.ColorNamePrimary: accentHue, + theme.ColorNameHyperlink: accentHue, + theme.ColorNameError: 25, + theme.ColorNameSuccess: 145, + theme.ColorNameWarning: 80, + } { + chroma := accentShare * gamutChroma(variant.accent, hue) + check(t, variant.name, name, parts.PaletteColour(name, variant.v), + labToRGB(variant.accent, chroma, hue), + "a colour carrying meaning stands at %.1f L* and %.0f%% of the %.1f chroma hue %.0f can reach", + variant.accent, accentShare*100, gamutChroma(variant.accent, hue), hue) + } + + // Selection is the accent's hue held down to a surface's lightness, so + // a chosen row belongs to the same family as the thing that chose it. + chroma := math.Min(selectionChroma, gamutChroma(variant.sel, accentHue)) + check(t, variant.name, theme.ColorNameSelection, + parts.PaletteColour(theme.ColorNameSelection, variant.v), + labToRGB(variant.sel, chroma, accentHue), + "the selection is the accent at %.1f L*", variant.sel) + } +} + +// The ladder climbs in even steps. +// +// Separate from the test above, because that one would pass a ladder whose +// rungs are 2, 9 and 3 apart as long as the table and the palette agreed on +// it. This is the property the eye actually reads: a surface is told from the +// one under it by the same distance everywhere, so nothing in the window is +// nearly the same colour as its neighbour by accident. +// +// The old palette failed this and that is why the rule exists: its steps were +// 5.9, 6.5, 1.4, 0.9 and 4.7 in L*, so three surfaces near the top - the +// separator, the menu and a button's face - stood within 2.3 L* of each other +// and read as one colour in three names. +func TestTheLadderClimbsInEvenSteps(t *testing.T) { + for _, variant := range []struct { + name string + v fyne.ThemeVariant + ranks []fyne.ThemeColorName + }{ + {"dark", theme.VariantDark, darkRanks}, + {"light", theme.VariantLight, lightRanks}, + } { + steps := make([]float64, 0, len(variant.ranks)-1) + for i := 1; i < len(variant.ranks); i++ { + under, _, _ := lch(parts.PaletteColour(variant.ranks[i-1], variant.v)) + over, _, _ := lch(parts.PaletteColour(variant.ranks[i], variant.v)) + steps = append(steps, math.Abs(over-under)) + } + + least, most := steps[0], steps[0] + for _, s := range steps { + least, most = math.Min(least, s), math.Max(most, s) + } + if most-least > 1.0 { + t.Errorf("%s: the rungs are %.1f apart at the tightest and %.1f at the widest, "+ + "so the ladder is not a ladder - %v", variant.name, least, most, steps) + } + if least < 5.0 { + t.Errorf("%s: two surfaces stand %.1f L* apart, under the 5 that reads as a step at all", + variant.name, least) + } + t.Logf("%s: rungs %v", variant.name, rounded(steps)) + } +} + +// The rule, in numbers. Candidate A of the three rendered for the owner on +// 2026-09-22 - the one that keeps the window's character and puts its +// relationships in order. +const ( + neutralHue = 291.0 // every surface and every ink + neutralChroma = 5.0 // the peak of the curve, at the middle of the ladder + accentHue = 259.0 // the blue a person presses + accentShare = 0.85 // of the chroma the hue can reach, not of a number + + // How much colour a chosen row carries. A ceiling rather than a share: the + // selection has to stay a SURFACE, and one at the accent's own chroma + // would shout louder than the accent standing on it. + selectionChroma = 22.0 +) + +var ( + darkLadder = map[fyne.ThemeColorName]float64{ + theme.ColorNameBackground: 11.3, + parts.ColorNamePanel: 17.8, + theme.ColorNameInputBackground: 24.3, + theme.ColorNameButton: 30.8, + theme.ColorNameSeparator: 30.8, // a line borrows the rank of a face + theme.ColorNameMenuBackground: 37.3, + theme.ColorNameInputBorder: 43.0, + theme.ColorNamePlaceHolder: 66.7, + theme.ColorNameDisabled: 80.3, + parts.ColorNameLabel: 84.5, + theme.ColorNameForeground: 91.3, + } + lightLadder = map[fyne.ThemeColorName]float64{ + theme.ColorNameBackground: 100.0, + parts.ColorNamePanel: 94.0, + theme.ColorNameInputBackground: 88.0, + theme.ColorNameButton: 82.0, + theme.ColorNameSeparator: 82.0, + theme.ColorNameMenuBackground: 100.0, // on white, what floats is white + theme.ColorNameInputBorder: 70.0, + theme.ColorNamePlaceHolder: 37.8, + theme.ColorNameDisabled: 25.8, + parts.ColorNameLabel: 28.5, + theme.ColorNameForeground: 9.3, + } + + // The surfaces in order, for the step test. The menu is left out of the + // light one on purpose: on a white page it is white, which is the page's + // own rank rather than a rung above the face below it. + darkRanks = []fyne.ThemeColorName{ + theme.ColorNameBackground, parts.ColorNamePanel, theme.ColorNameInputBackground, + theme.ColorNameButton, theme.ColorNameMenuBackground, theme.ColorNameInputBorder, + } + lightRanks = []fyne.ThemeColorName{ + theme.ColorNameBackground, parts.ColorNamePanel, theme.ColorNameInputBackground, + theme.ColorNameButton, + } +) + +// gamutChroma is how much chroma a hue has in sRGB at a lightness, found by +// halving - the same way tools/probes/palette/design.py finds it, because a +// requested chroma outside the gamut would be clipped into a different HUE, +// which is the one thing this palette holds constant. +func gamutChroma(L, hue float64) float64 { + low, high := 0.0, 150.0 + for i := 0; i < 40; i++ { + mid := (low + high) / 2 + if inGamut(L, mid, hue) { + low = mid + } else { + high = mid + } + } + _, c, _ := lch(labToRGB(L, low, hue)) + return c +} + +// The colour arithmetic this file needs beyond what palette_test.go already +// has. That one asks about lightness and contrast, which both fall out of +// luminance alone. A hue and a chroma need the other two axes, so the +// conversion is written out here. +// +// CIE Lab, D65, the same white point and the same matrices as the probe in +// tools/probes/palette - and the two agreeing is not assumed: the values in +// the palette were produced by the probe and are checked here, so a +// disagreement between the two shows up as a red test rather than as a colour +// nobody questions. +func lch(c color.Color) (float64, float64, float64) { + r, g, b := channels(c) + x, y, z := xyz(linear(r), linear(g), linear(b)) + f := func(t float64) float64 { + if t > 0.008856 { + return math.Cbrt(t) + } + return 7.787*t + 16.0/116.0 + } + fx, fy, fz := f(x/whiteX), f(y), f(z/whiteZ) + lightness := 116*fy - 16 + a, bb := 500*(fx-fy), 200*(fy-fz) + return lightness, math.Hypot(a, bb), math.Mod(math.Atan2(bb, a)*180/math.Pi+360, 360) +} + +func xyz(r, g, b float64) (float64, float64, float64) { + return 0.4124564*r + 0.3575761*g + 0.1804375*b, + 0.2126729*r + 0.7151522*g + 0.0721750*b, + 0.0193339*r + 0.1191920*g + 0.9503041*b +} + +// labChannels is a lightness, chroma and hue back in linear sRGB, before +// anything is clamped - so inGamut can ask whether the colour exists. +func labChannels(l, c, h float64) (float64, float64, float64) { + a := c * math.Cos(h*math.Pi/180) + bb := c * math.Sin(h*math.Pi/180) + fy := (l + 16) / 116 + fx, fz := fy+a/500, fy-bb/200 + back := func(t float64) float64 { + if t*t*t > 0.008856 { + return t * t * t + } + return (t - 16.0/116.0) / 7.787 + } + x, y, z := back(fx)*whiteX, back(fy), back(fz)*whiteZ + return gamma(3.2404542*x - 1.5371385*y - 0.4985314*z), + gamma(-0.9692660*x + 1.8760108*y + 0.0415560*z), + gamma(0.0556434*x - 0.2040259*y + 1.0572252*z) +} + +func gamma(v float64) float64 { + if v <= 0.0031308 { + return 12.92 * v + } + return 1.055*math.Pow(v, 1/2.4) - 0.055 +} + +func inGamut(l, c, h float64) bool { + r, g, b := labChannels(l, c, h) + for _, v := range []float64{r, g, b} { + if v < -0.0005 || v > 1.0005 { + return false + } + } + return true +} + +func labToRGB(l, c, h float64) color.Color { + r, g, b := labChannels(l, c, h) + to8 := func(v float64) uint8 { + return uint8(math.Round(math.Max(0, math.Min(1, v)) * 255)) + } + return color.NRGBA{R: to8(r), G: to8(g), B: to8(b), A: 0xFF} +} + +func rounded(values []float64) []float64 { + out := make([]float64, len(values)) + for i, v := range values { + out[i] = math.Round(v*10) / 10 + } + return out +} + +const ( + whiteX = 0.95047 + whiteZ = 1.08883 +) + +// check holds one colour of the palette against the one the rule produces. +// +// A byte either way is allowed and nothing more. The two conversions - this +// one and the probe's in tools/probes/palette - halve their way into the sRGB +// gamut in floating point and then round, so the last bit is not something +// either of them promises. Anything a person would call a different colour is +// several bytes away, and the whole point of the tolerance being this tight is +// that a nudge "just a shade lighter" lands outside it. +func check(t *testing.T, variant string, name fyne.ThemeColorName, got, want color.Color, why string, args ...any) { + t.Helper() + gotR, gotG, gotB, _ := got.RGBA() + wantR, wantG, wantB, _ := want.RGBA() + off := func(a, b uint32) bool { return math.Abs(float64(a>>8)-float64(b>>8)) > 1 } + if off(gotR, wantR) || off(gotG, wantG) || off(gotB, wantB) { + L, C, h := lch(got) + wantL, wantC, wantH := lch(want) + t.Errorf("%s: %s is #%02X%02X%02X (%.1f L*, %.2f C*, %.0f h) and the rule produces "+ + "#%02X%02X%02X (%.1f L*, %.2f C*, %.0f h) - "+why, + append([]any{variant, name, gotR >> 8, gotG >> 8, gotB >> 8, L, C, h, + wantR >> 8, wantG >> 8, wantB >> 8, wantL, wantC, wantH}, args...)...) + } +} diff --git a/internal/guard/sectionsurface_test.go b/internal/guard/sectionsurface_test.go index 74de118..9dc0115 100644 --- a/internal/guard/sectionsurface_test.go +++ b/internal/guard/sectionsurface_test.go @@ -142,11 +142,44 @@ func TestEachSurfaceIsToldFromTheOneUnderIt(t *testing.T) { // is on the page any more - it is on a panel, which is lighter. That is // the same shape as the defect the palette guard caught on its first // day: a colour measured against the wrong thing. - for _, name := range []fyne.ThemeColorName{theme.ColorNameForeground, theme.ColorNamePlaceHolder} { - if got := contrast(parts.PaletteColour(name, variant.v), panel); got < 4.5 { - t.Errorf("%s: %s is %.2f:1 on a panel, under the 4.5 a reader needs", - variant.name, name, got) + // + // And it moved again, twice, without this guard noticing either time - + // found in the review of #124 and measured on 2026-09-23. An ink is + // asked about against the LIGHTEST surface it is drawn on, which for + // three of these is not the panel: + // + // - a hint and a switched-off value sit INSIDE a box, which is a + // rank lighter than the panel. The hint reads 5.56:1 against the + // panel and 4.52:1 against the box it is really in. The panel + // number has a whole ratio of slack over the threshold and the + // real one has 0.02, so the comfortable measurement was hiding + // exactly the margin somebody would want to know about. + // - a value is also drawn on the floating surface, because that is + // what a row of an open list is (parts/listrow.go draws its words + // in Foreground). That surface went up 6.4 L* when the ladder + // arrived, which took this ratio from 7.27 to 5.74 - still over, + // and nothing here was watching it. + // + // The pairs are named rather than derived, because where an ink is + // drawn is a fact about the window rather than about the palette. A + // new ink with no pair here is not measured by this guard at all, + // which is the limit of it and is why it says so out loud. + for _, ink := range []struct { + name fyne.ThemeColorName + surface fyne.ThemeColorName + where string + }{ + {theme.ColorNameForeground, theme.ColorNameMenuBackground, "a row of an open list"}, + {parts.ColorNameLabel, parts.ColorNamePanel, "a panel"}, + {theme.ColorNameDisabled, theme.ColorNameInputBackground, "a box to type in"}, + {theme.ColorNamePlaceHolder, theme.ColorNameInputBackground, "a box to type in"}, + } { + got := contrast(parts.PaletteColour(ink.name, variant.v), parts.PaletteColour(ink.surface, variant.v)) + if got < 4.5 { + t.Errorf("%s: %s is %.2f:1 on %s, which is the lightest surface it is drawn on, "+ + "under the 4.5 a reader needs", variant.name, ink.name, got, ink.where) } + t.Logf("%s: %s on %s is %.2f:1", variant.name, ink.name, ink.where, got) } t.Logf("%s: page to panel %.1f L*, panel to input %.1f L*, of %.1f available", diff --git a/internal/guard/testdata/screens/about.png b/internal/guard/testdata/screens/about.png index 3103140..4945114 100644 Binary files a/internal/guard/testdata/screens/about.png and b/internal/guard/testdata/screens/about.png differ diff --git a/internal/guard/testdata/screens/about.xml b/internal/guard/testdata/screens/about.xml index 87fe9d6..f0030be 100644 --- a/internal/guard/testdata/screens/about.xml +++ b/internal/guard/testdata/screens/about.xml @@ -1,7 +1,7 @@ - + @@ -56,7 +56,7 @@ - + Licence @@ -90,7 +90,7 @@ - + Support @@ -116,7 +116,7 @@ - + Third party code compiled in @@ -164,7 +164,7 @@ - + Files compiled in that are not code @@ -189,7 +189,7 @@ - + Shipped beside the program on Windows @@ -218,7 +218,7 @@ - + diff --git a/internal/guard/testdata/screens/catalogue.png b/internal/guard/testdata/screens/catalogue.png index a5d73d2..890dcc1 100644 Binary files a/internal/guard/testdata/screens/catalogue.png and b/internal/guard/testdata/screens/catalogue.png differ diff --git a/internal/guard/testdata/screens/catalogue.xml b/internal/guard/testdata/screens/catalogue.xml index b57ecff..87bfbf0 100644 --- a/internal/guard/testdata/screens/catalogue.xml +++ b/internal/guard/testdata/screens/catalogue.xml @@ -1,7 +1,7 @@ - + - - + + @@ -25,7 +25,7 @@ - + Button @@ -60,7 +60,7 @@ - + Generate @@ -78,7 +78,7 @@ - + Generate @@ -132,7 +132,7 @@ - + Generate @@ -150,7 +150,7 @@ - + Generate @@ -186,7 +186,7 @@ - + Generate @@ -276,7 +276,7 @@ - + Generate @@ -366,7 +366,7 @@ - + @@ -375,7 +375,7 @@ - + Chooser @@ -539,7 +539,7 @@ - + Entry @@ -661,7 +661,7 @@ - + Toggle @@ -761,7 +761,7 @@ - + Segments @@ -911,7 +911,7 @@ - + OpenList @@ -955,13 +955,13 @@ - + - + - + @@ -1012,13 +1012,13 @@ - + - + - + @@ -1081,19 +1081,19 @@ - + - + - + - + - + @@ -1219,40 +1219,40 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -1298,10 +1298,10 @@ - + - + @@ -1317,7 +1317,7 @@ - + Tabs @@ -1434,7 +1434,7 @@ - + @@ -1501,7 +1501,7 @@ - + Fields @@ -1517,7 +1517,7 @@ - Size + Size @@ -1550,7 +1550,7 @@ - Size + Size * @@ -1586,7 +1586,7 @@ - Size + Size @@ -1629,7 +1629,7 @@ - Size + Size @@ -1661,7 +1661,7 @@ - Label in each file + Label in each file @@ -1682,9 +1682,9 @@ - Name + Name - Size + Size * @@ -1838,7 +1838,7 @@ - Write a label inside each generated file, including the ones that are far too small to hold it + Write a label inside each generated file, including the ones that are far too small to hold it @@ -1863,7 +1863,7 @@ - + PropertyField @@ -1882,7 +1882,7 @@ - Width + Width @@ -1930,7 +1930,7 @@ - Compression + Compression @@ -1974,7 +1974,7 @@ - Bom + Bom @@ -2008,7 +2008,7 @@ - Member size + Member size @@ -2065,7 +2065,7 @@ - Password + Password @@ -2102,7 +2102,7 @@ - + ByteCount @@ -2118,7 +2118,7 @@ - Size + Size @@ -2159,7 +2159,7 @@ - Size + Size @@ -2203,7 +2203,7 @@ - Size + Size @@ -2236,7 +2236,7 @@ - + Tips @@ -2254,7 +2254,7 @@ - Size + Size @@ -2300,7 +2300,7 @@ - Size + Size @@ -2354,7 +2354,7 @@ - + ErrorArea @@ -2418,7 +2418,7 @@ - + Progress @@ -2471,7 +2471,7 @@ - + Folding @@ -2486,7 +2486,7 @@ - + @@ -2539,7 +2539,7 @@ - + @@ -2581,7 +2581,7 @@ - + @@ -2713,7 +2713,7 @@ - + @@ -2766,7 +2766,7 @@ - + @@ -2819,7 +2819,7 @@ - + @@ -2872,7 +2872,7 @@ - + @@ -2912,7 +2912,7 @@ - + Title @@ -2997,7 +2997,7 @@ - Output directory + Output directory @@ -3148,7 +3148,7 @@ - Write a label inside each generated file, including the ones that are far too small to hold it + Write a label inside each generated file, including the ones that are far too small to hold it @@ -3182,7 +3182,7 @@ - + Section @@ -3197,13 +3197,13 @@ - + File configuration - Size + Size @@ -3225,7 +3225,7 @@ - How many files + How many files @@ -3260,13 +3260,13 @@ - + Output - Size + Size @@ -3290,7 +3290,7 @@ - How many files + How many files @@ -3325,7 +3325,7 @@ - + @@ -3372,13 +3372,13 @@ - + Write a label inside each generated file, including the ones that are far too small to hold it - Size + Size @@ -3400,7 +3400,7 @@ - How many files + How many files @@ -3427,8 +3427,1498 @@ - - + + + + + Swatch + + + + + + a surface near the bottom of the ladder + + + + + + + + + + + + + + a surface near the top of it + + + + + + + + + + + + + + a colour that carries meaning + + + + + + + + + + + + + + an ink, which is nearly white + + + + + + + + + + + + + + something the toolkit blends, laid over a panel first + + + + + + + + + + + + + + + + Palette, as this window draws it + + + + + Every colour the window paints with, measured against what it is drawn on. + + + + + + + + + The ladder of surfaces + + + + + + + + + + + + background - the page the window stands on + + + + + + + + + #1E1E20 + + + + + + + + + + + + + + panel - the surface a section is drawn on + + + + + + + + + #2B2B2F - 17.7 L*, +6.3 from the page + + + + + + + + + + + + + + inputBackground - the fill of a box to type in + + + + + + + + + #39393F - 24.2 L*, +6.5 from the panel + + + + + + + + + + + + + + button - the face of a button that is not the main one + + + + + + + + + #48484E - 30.8 L*, +6.6 from a box to type in + + + + + + + + + + + + + + separator - a line that separates and says nothing else + + + + + + + + + #48484E - 30.8 L*, +13.1 from the panel + + + + + + + + + + + + + + menuBackground - what an open list floats on + + + + + + + + + #57575F - 37.2 L*, +13.1 from a box to type in + + + + + + + + + + + + + + inputBorder - the edge that says where the typing goes + + + + + + + + + #65656D - 43.0 L*, +18.9 from its own fill + + + + + + + + + + + The inks + + + + + + + + + + + + foreground - a value, and everything read at rest + + + + + + + + + #E6E6E8 - 5.74:1 on an open list + + + + + + + + + + + + + + label - the name of a field, a step quieter than its value + + + + + + + + + #D3D3D7 - 9.45:1 on a panel + + + + + + + + + + + + + + disabled - a value in a box switched off for the length of a run + + + + + + + + + #C7C7CC - 6.81:1 on a box to type in + + + + + + + + + + + + + + placeholder - a hint in a box nobody has typed in + + + + + + + + + #A2A2A9 - 4.52:1 on a box to type in + + + + + + + + + + + What carries meaning + + + + + + + + + + + + primary - the action a screen is for, and the line round a focused control + + + + + + + + + #5AB3F2 - 7.26:1 on the page + + + + + + + + + + + + + + hyperlink - a link, the same blue as the action + + + + + + + + + #5AB3F2 - 7.26:1 on the page + + + + + + + + + + + + + + error - a refusal, and a file that failed + + + + + + + + + #F48F8D - 7.26:1 on the page + + + + + + + + + + + + + + success - a run that wrote everything it promised + + + + + + + + + #42C260 - 7.23:1 on the page + + + + + + + + + + + + + + warning - a run that wrote files and skipped some + + + + + + + + + #DAA131 - 7.23:1 on the page + + + + + + + + + + + + + + selection - the row or the segment somebody chose + + + + + + + + + #224B69 - 30.4 L*, +19.0 from the page + + + + + + + + + + + What the pointer, a press and the keyboard do + + + + + + + + + + + + hover - what the pointer does to a dark face + + + + + + + + + #FFFFFF at 0x22 - over the page it comes to #3C3C3D, 25.4 L*, a move of +14.0 + + + + + + + + + + + + + + pressed - what a press does to a dark face + + + + + + + + + #FFFFFF at 0x40 - over a button's face it comes to #76767A, 49.7 L*, a move of +19.0 + + + + + + + + + + + + + + lift - what the pointer does to the filled main button + + + + + + + + + #FFFFFF at 0x66 - over the main button it comes to #9CD2F8, 81.6 L*, a move of +11.6 + + + + + + + + + + + + + + shade - what a press does to the filled main button + + + + + + + + + #000000 at 0x33 - over the main button it comes to #488FC2, 57.0 L*, a move of -13.0 + + + + + + + + + + + + + + focus - the wash the toolkit lays over a control the keyboard is in + + + + + + + + + #00A5F0 at 0x66 - over a box to type in it comes to #226486, 39.9 L*, a move of +15.8 + + + + + + + + + + + + + + tipshade - the shade an explanation casts on the form under it + + + + + + + + + #000000 at 0x66 - over a panel it comes to #19191C, 9.3 L*, a move of -8.4 + + + + + + + + + + + + + + shadow - what a popup casts - nothing here, on purpose + + + + + + + + + nothing - drawn transparent on purpose + + + + + + + + + + + What is written on a filled face + + + + + + + + + + + + foregroundOnPrimary - the word on the main button + + + + + + + + + #1A1A1A - 7.60:1 on the main button + + + + + + + + + + + + + + foregroundOnError - what is written on a filled red + + + + + + + + + #1A1A1A - 7.59:1 on a filled red + + + + + + + + + + + + + + foregroundOnSuccess - what is written on a filled green + + + + + + + + + #1A1A1A - 7.56:1 on a filled green + + + + + + + + + + + + + + foregroundOnWarning - what is written on a filled amber + + + + + + + + + #1A1A1A - 7.56:1 on a filled amber + + + + + + + + + + + + + + Palette, light - computed and not installed + + + + + Worked out and guarded to the same thresholds. The window does not use it: this program has one look. + + + + + + + + + The ladder of surfaces + + + + + + + + + + + + background - the page the window stands on + + + + + + + + + #FFFFFF + + + + + + + + + + + + + + panel - the surface a section is drawn on + + + + + + + + + #EEEEEF - 94.1 L*, -5.9 from the page + + + + + + + + + + + + + + inputBackground - the fill of a box to type in + + + + + + + + + #DCDCE0 - 87.9 L*, -6.3 from the panel + + + + + + + + + + + + + + button - the face of a button that is not the main one + + + + + + + + + #CCCBD1 - 81.9 L*, -5.9 from a box to type in + + + + + + + + + + + + + + separator - a line that separates and says nothing else + + + + + + + + + #CCCBD1 - 81.9 L*, -12.2 from the panel + + + + + + + + + + + + + + menuBackground - what an open list floats on + + + + + + + + + #FFFFFF - 100.0 L*, +12.1 from a box to type in + + + + + + + + + + + + + + inputBorder - the edge that says where the typing goes + + + + + + + + + #ABAAB2 - 69.9 L*, -18.0 from its own fill + + + + + + + + + + + The inks + + + + + + + + + + + + foreground - a value, and everything read at rest + + + + + + + + + #1A1A1C - 17.38:1 on an open list + + + + + + + + + + + + + + label - the name of a field, a step quieter than its value + + + + + + + + + #434349 - 8.47:1 on a panel + + + + + + + + + + + + + + disabled - a value in a box switched off for the length of a run + + + + + + + + + #3D3D42 - 7.90:1 on a box to type in + + + + + + + + + + + + + + placeholder - a hint in a box nobody has typed in + + + + + + + + + #585860 - 5.15:1 on a box to type in + + + + + + + + + + + What carries meaning + + + + + + + + + + + + primary - the action a screen is for, and the line round a focused control + + + + + + + + + #286893 - 6.01:1 on the page + + + + + + + + + + + + + + hyperlink - a link, the same blue as the action + + + + + + + + + #286893 - 6.01:1 on the page + + + + + + + + + + + + + + error - a refusal, and a file that failed + + + + + + + + + #BB293B - 6.01:1 on the page + + + + + + + + + + + + + + success - a run that wrote everything it promised + + + + + + + + + #237135 - 6.03:1 on the page + + + + + + + + + + + + + + warning - a run that wrote files and skipped some + + + + + + + + + #805D18 - 6.01:1 on the page + + + + + + + + + + + + + + selection - the row or the segment somebody chose + + + + + + + + + #C1E0FF - 87.9 L*, -12.1 from the page + + + + + + + + + + + What the pointer, a press and the keyboard do + + + + + + + + + + + + hover - what the pointer does to a dark face + + + + + + + + + #000000 at 0x20 - over the page it comes to #DFDFDF, 88.8 L*, a move of -11.2 + + + + + + + + + + + + + + pressed - what a press does to a dark face + + + + + + + + + #000000 at 0x33 - over a button's face it comes to #A3A3A7, 67.0 L*, a move of -15.0 + + + + + + + + + + + + + + lift - what the pointer does to the filled main button + + + + + + + + + #FFFFFF at 0x66 - over the main button it comes to #7EA5BE, 65.6 L*, a move of +23.7 + + + + + + + + + + + + + + shade - what a press does to the filled main button + + + + + + + + + #000000 at 0x33 - over the main button it comes to #205376, 33.6 L*, a move of -8.3 + + + + + + + + + + + + + + focus - the wash the toolkit lays over a control the keyboard is in + + + + + + + + + #007FBA at 0x66 - over a box to type in it comes to #84B7D1, 71.7 L*, a move of -16.1 + + + + + + + + + + + + + + tipshade - the shade an explanation casts on the form under it + + + + + + + + + #000000 at 0x40 - over a panel it comes to #B2B2B3, 72.7 L*, a move of -21.4 + + + + + + + + + + + + + + shadow - what a popup casts - nothing here, on purpose + + + + + + + + + #000000 at 0x40 + + + + + + + + + + + What is written on a filled face + + + + + + + + + + + + foregroundOnPrimary - the word on the main button + + + + + + + + + #FFFFFF - 6.01:1 on the main button + + + + + + + + + + + + + + foregroundOnError - what is written on a filled red + + + + + + + + + #FFFFFF - 6.01:1 on a filled red + + + + + + + + + + + + + + foregroundOnSuccess - what is written on a filled green + + + + + + + + + #FFFFFF - 6.03:1 on a filled green + + + + + + + + + + + + + + foregroundOnWarning - what is written on a filled amber + + + + + + + + + #FFFFFF - 6.01:1 on a filled amber + + + + + + + + + + + Not drawn, and why @@ -3545,8 +5035,8 @@ - - + + Layout only, and why diff --git a/internal/guard/testdata/screens/generate-chosen-by-key.png b/internal/guard/testdata/screens/generate-chosen-by-key.png index 8dc7fca..082a912 100644 Binary files a/internal/guard/testdata/screens/generate-chosen-by-key.png and b/internal/guard/testdata/screens/generate-chosen-by-key.png differ diff --git a/internal/guard/testdata/screens/generate-chosen-by-key.xml b/internal/guard/testdata/screens/generate-chosen-by-key.xml index 365930c..a18b142 100644 --- a/internal/guard/testdata/screens/generate-chosen-by-key.xml +++ b/internal/guard/testdata/screens/generate-chosen-by-key.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-chosen.png b/internal/guard/testdata/screens/generate-chosen.png index f748899..c46e3a8 100644 Binary files a/internal/guard/testdata/screens/generate-chosen.png and b/internal/guard/testdata/screens/generate-chosen.png differ diff --git a/internal/guard/testdata/screens/generate-chosen.xml b/internal/guard/testdata/screens/generate-chosen.xml index ec000f3..659fb12 100644 --- a/internal/guard/testdata/screens/generate-chosen.xml +++ b/internal/guard/testdata/screens/generate-chosen.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-empty.png b/internal/guard/testdata/screens/generate-empty.png index 7150779..12f86c9 100644 Binary files a/internal/guard/testdata/screens/generate-empty.png and b/internal/guard/testdata/screens/generate-empty.png differ diff --git a/internal/guard/testdata/screens/generate-empty.xml b/internal/guard/testdata/screens/generate-empty.xml index a60ccb4..48b1a8e 100644 --- a/internal/guard/testdata/screens/generate-empty.xml +++ b/internal/guard/testdata/screens/generate-empty.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -169,7 +169,7 @@ - Batch name + Batch name * @@ -200,7 +200,7 @@ - File names + File names @@ -259,7 +259,7 @@ - Damage + Damage @@ -289,14 +289,14 @@ - + Output - Output directory + Output directory * @@ -332,7 +332,7 @@ - Seed + Seed * @@ -363,7 +363,7 @@ - Label in each file + Label in each file @@ -385,7 +385,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-focused.png b/internal/guard/testdata/screens/generate-focused.png index 6cd29f6..d79b289 100644 Binary files a/internal/guard/testdata/screens/generate-focused.png and b/internal/guard/testdata/screens/generate-focused.png differ diff --git a/internal/guard/testdata/screens/generate-focused.xml b/internal/guard/testdata/screens/generate-focused.xml index c0e610a..4cf27f1 100644 --- a/internal/guard/testdata/screens/generate-focused.xml +++ b/internal/guard/testdata/screens/generate-focused.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -132,7 +132,7 @@ - How many files + How many files * @@ -158,7 +158,7 @@ - Batch name + Batch name * @@ -189,7 +189,7 @@ - File names + File names @@ -248,7 +248,7 @@ - Damage + Damage @@ -278,14 +278,14 @@ - + Output - Output directory + Output directory * @@ -321,7 +321,7 @@ - Seed + Seed * @@ -352,7 +352,7 @@ - Label in each file + Label in each file @@ -374,7 +374,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-hovered.png b/internal/guard/testdata/screens/generate-hovered.png index 7307353..e092588 100644 Binary files a/internal/guard/testdata/screens/generate-hovered.png and b/internal/guard/testdata/screens/generate-hovered.png differ diff --git a/internal/guard/testdata/screens/generate-hovered.xml b/internal/guard/testdata/screens/generate-hovered.xml index fc36fb3..ece703d 100644 --- a/internal/guard/testdata/screens/generate-hovered.xml +++ b/internal/guard/testdata/screens/generate-hovered.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-menu-hovered.png b/internal/guard/testdata/screens/generate-menu-hovered.png index 1b0eb6c..5b44d6b 100644 Binary files a/internal/guard/testdata/screens/generate-menu-hovered.png and b/internal/guard/testdata/screens/generate-menu-hovered.png differ diff --git a/internal/guard/testdata/screens/generate-menu-hovered.xml b/internal/guard/testdata/screens/generate-menu-hovered.xml index f6fb6f7..3324800 100644 --- a/internal/guard/testdata/screens/generate-menu-hovered.xml +++ b/internal/guard/testdata/screens/generate-menu-hovered.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + @@ -611,76 +611,76 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/internal/guard/testdata/screens/generate-menu-keyed.png b/internal/guard/testdata/screens/generate-menu-keyed.png index 483bae0..7f41e3a 100644 Binary files a/internal/guard/testdata/screens/generate-menu-keyed.png and b/internal/guard/testdata/screens/generate-menu-keyed.png differ diff --git a/internal/guard/testdata/screens/generate-menu-keyed.xml b/internal/guard/testdata/screens/generate-menu-keyed.xml index dfbeff4..c22a992 100644 --- a/internal/guard/testdata/screens/generate-menu-keyed.xml +++ b/internal/guard/testdata/screens/generate-menu-keyed.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + @@ -611,76 +611,76 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/internal/guard/testdata/screens/generate-menu.png b/internal/guard/testdata/screens/generate-menu.png index 1106dff..6e750b9 100644 Binary files a/internal/guard/testdata/screens/generate-menu.png and b/internal/guard/testdata/screens/generate-menu.png differ diff --git a/internal/guard/testdata/screens/generate-menu.xml b/internal/guard/testdata/screens/generate-menu.xml index e8a1fc9..1f5e338 100644 --- a/internal/guard/testdata/screens/generate-menu.xml +++ b/internal/guard/testdata/screens/generate-menu.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + @@ -611,76 +611,76 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/internal/guard/testdata/screens/generate-refused-both.png b/internal/guard/testdata/screens/generate-refused-both.png index 39a12bd..da2ab98 100644 Binary files a/internal/guard/testdata/screens/generate-refused-both.png and b/internal/guard/testdata/screens/generate-refused-both.png differ diff --git a/internal/guard/testdata/screens/generate-refused-both.xml b/internal/guard/testdata/screens/generate-refused-both.xml index 61be050..492dacd 100644 --- a/internal/guard/testdata/screens/generate-refused-both.xml +++ b/internal/guard/testdata/screens/generate-refused-both.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -143,7 +143,7 @@ - How many files + How many files * @@ -180,7 +180,7 @@ - Batch name + Batch name * @@ -211,7 +211,7 @@ - File names + File names @@ -270,7 +270,7 @@ - Damage + Damage @@ -300,14 +300,14 @@ - + Output - Output directory + Output directory * @@ -343,7 +343,7 @@ - Seed + Seed * @@ -374,7 +374,7 @@ - Label in each file + Label in each file @@ -396,7 +396,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-refused-setting.png b/internal/guard/testdata/screens/generate-refused-setting.png index de04df7..d87a266 100644 Binary files a/internal/guard/testdata/screens/generate-refused-setting.png and b/internal/guard/testdata/screens/generate-refused-setting.png differ diff --git a/internal/guard/testdata/screens/generate-refused-setting.xml b/internal/guard/testdata/screens/generate-refused-setting.xml index 3e16629..56b29e7 100644 --- a/internal/guard/testdata/screens/generate-refused-setting.xml +++ b/internal/guard/testdata/screens/generate-refused-setting.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -246,7 +246,7 @@ - Width + Width @@ -286,7 +286,7 @@ - Height + Height @@ -329,7 +329,7 @@ - Damage + Damage @@ -359,14 +359,14 @@ - + Output - Output directory + Output directory * @@ -402,7 +402,7 @@ - Seed + Seed * @@ -433,7 +433,7 @@ - Label in each file + Label in each file @@ -463,7 +463,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-refused.png b/internal/guard/testdata/screens/generate-refused.png index f4ed6a7..07ba339 100644 Binary files a/internal/guard/testdata/screens/generate-refused.png and b/internal/guard/testdata/screens/generate-refused.png differ diff --git a/internal/guard/testdata/screens/generate-refused.xml b/internal/guard/testdata/screens/generate-refused.xml index 97381cf..0e7ca80 100644 --- a/internal/guard/testdata/screens/generate-refused.xml +++ b/internal/guard/testdata/screens/generate-refused.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -145,7 +145,7 @@ - How many files + How many files * @@ -171,7 +171,7 @@ - Batch name + Batch name * @@ -202,7 +202,7 @@ - File names + File names @@ -261,7 +261,7 @@ - Damage + Damage @@ -291,14 +291,14 @@ - + Output - Output directory + Output directory * @@ -334,7 +334,7 @@ - Seed + Seed * @@ -365,7 +365,7 @@ - Label in each file + Label in each file @@ -387,7 +387,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-switch-by-key.png b/internal/guard/testdata/screens/generate-switch-by-key.png index 5c67f52..01dc4b6 100644 Binary files a/internal/guard/testdata/screens/generate-switch-by-key.png and b/internal/guard/testdata/screens/generate-switch-by-key.png differ diff --git a/internal/guard/testdata/screens/generate-switch-by-key.xml b/internal/guard/testdata/screens/generate-switch-by-key.xml index a81dbd4..1d72b60 100644 --- a/internal/guard/testdata/screens/generate-switch-by-key.xml +++ b/internal/guard/testdata/screens/generate-switch-by-key.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-typed.png b/internal/guard/testdata/screens/generate-typed.png index af4dd9f..eaac951 100644 Binary files a/internal/guard/testdata/screens/generate-typed.png and b/internal/guard/testdata/screens/generate-typed.png differ diff --git a/internal/guard/testdata/screens/generate-typed.xml b/internal/guard/testdata/screens/generate-typed.xml index 2f11d34..cf5994b 100644 --- a/internal/guard/testdata/screens/generate-typed.xml +++ b/internal/guard/testdata/screens/generate-typed.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -142,7 +142,7 @@ - How many files + How many files * @@ -168,7 +168,7 @@ - Batch name + Batch name * @@ -199,7 +199,7 @@ - File names + File names @@ -258,7 +258,7 @@ - Damage + Damage @@ -288,14 +288,14 @@ - + Output - Output directory + Output directory * @@ -331,7 +331,7 @@ - Seed + Seed * @@ -362,7 +362,7 @@ - Label in each file + Label in each file @@ -384,7 +384,7 @@ - + diff --git a/internal/guard/testdata/screens/generate-unchecked.png b/internal/guard/testdata/screens/generate-unchecked.png index ef8e986..d773ec8 100644 Binary files a/internal/guard/testdata/screens/generate-unchecked.png and b/internal/guard/testdata/screens/generate-unchecked.png differ diff --git a/internal/guard/testdata/screens/generate-unchecked.xml b/internal/guard/testdata/screens/generate-unchecked.xml index aad8e19..382b022 100644 --- a/internal/guard/testdata/screens/generate-unchecked.xml +++ b/internal/guard/testdata/screens/generate-unchecked.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -372,7 +372,7 @@ - + diff --git a/internal/guard/testdata/screens/generate.png b/internal/guard/testdata/screens/generate.png index 44029b4..2f6d807 100644 Binary files a/internal/guard/testdata/screens/generate.png and b/internal/guard/testdata/screens/generate.png differ diff --git a/internal/guard/testdata/screens/generate.xml b/internal/guard/testdata/screens/generate.xml index 46d1924..bbed224 100644 --- a/internal/guard/testdata/screens/generate.xml +++ b/internal/guard/testdata/screens/generate.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + File configuration - Format + Format @@ -91,7 +91,7 @@ - Size + Size * @@ -131,7 +131,7 @@ - How many files + How many files * @@ -157,7 +157,7 @@ - Batch name + Batch name * @@ -188,7 +188,7 @@ - File names + File names @@ -247,7 +247,7 @@ - Damage + Damage @@ -277,14 +277,14 @@ - + Output - Output directory + Output directory * @@ -320,7 +320,7 @@ - Seed + Seed * @@ -351,7 +351,7 @@ - Label in each file + Label in each file @@ -373,7 +373,7 @@ - + diff --git a/internal/guard/testdata/screens/preset-many-settings.png b/internal/guard/testdata/screens/preset-many-settings.png index 070d9bc..66ac53a 100644 Binary files a/internal/guard/testdata/screens/preset-many-settings.png and b/internal/guard/testdata/screens/preset-many-settings.png differ diff --git a/internal/guard/testdata/screens/preset-many-settings.xml b/internal/guard/testdata/screens/preset-many-settings.xml index a529c7d..e1eaae4 100644 --- a/internal/guard/testdata/screens/preset-many-settings.xml +++ b/internal/guard/testdata/screens/preset-many-settings.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + The question - Preset + Preset @@ -186,7 +186,7 @@ - + Settings @@ -194,7 +194,7 @@ - Limit + Limit @@ -234,7 +234,7 @@ - Allow + Allow @@ -265,7 +265,7 @@ - Deny + Deny @@ -296,7 +296,7 @@ - Far-over + Far-over @@ -323,7 +323,7 @@ - Bulk + Bulk @@ -356,14 +356,14 @@ - + Output - Output directory + Output directory * @@ -399,7 +399,7 @@ - Seed + Seed * @@ -434,7 +434,7 @@ - + diff --git a/internal/guard/testdata/screens/preset-menu-setting.png b/internal/guard/testdata/screens/preset-menu-setting.png index a0a091e..f0d3832 100644 Binary files a/internal/guard/testdata/screens/preset-menu-setting.png and b/internal/guard/testdata/screens/preset-menu-setting.png differ diff --git a/internal/guard/testdata/screens/preset-menu-setting.xml b/internal/guard/testdata/screens/preset-menu-setting.xml index 8e95875..06bad88 100644 --- a/internal/guard/testdata/screens/preset-menu-setting.xml +++ b/internal/guard/testdata/screens/preset-menu-setting.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + The question - Preset + Preset @@ -150,7 +150,7 @@ - + Settings @@ -158,7 +158,7 @@ - Limit + Limit @@ -198,7 +198,7 @@ - Spread + Spread @@ -229,7 +229,7 @@ - Format + Format @@ -258,14 +258,14 @@ - + Output - Output directory + Output directory * @@ -301,7 +301,7 @@ - Seed + Seed * @@ -336,7 +336,7 @@ - + @@ -553,67 +553,67 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/internal/guard/testdata/screens/preset-menu.png b/internal/guard/testdata/screens/preset-menu.png index d7984cb..c680137 100644 Binary files a/internal/guard/testdata/screens/preset-menu.png and b/internal/guard/testdata/screens/preset-menu.png differ diff --git a/internal/guard/testdata/screens/preset-menu.xml b/internal/guard/testdata/screens/preset-menu.xml index 2e7b858..9006bee 100644 --- a/internal/guard/testdata/screens/preset-menu.xml +++ b/internal/guard/testdata/screens/preset-menu.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + The question - Preset + Preset @@ -162,7 +162,7 @@ - + Settings @@ -170,7 +170,7 @@ - Formats + Formats @@ -203,14 +203,14 @@ - + Output - Output directory + Output directory * @@ -246,7 +246,7 @@ - Seed + Seed * @@ -281,7 +281,7 @@ - + @@ -382,19 +382,19 @@ - + - + - + - + - + diff --git a/internal/guard/testdata/screens/preset-refused.png b/internal/guard/testdata/screens/preset-refused.png index cc9a76e..b9ff8c6 100644 Binary files a/internal/guard/testdata/screens/preset-refused.png and b/internal/guard/testdata/screens/preset-refused.png differ diff --git a/internal/guard/testdata/screens/preset-refused.xml b/internal/guard/testdata/screens/preset-refused.xml index c36e282..c89861f 100644 --- a/internal/guard/testdata/screens/preset-refused.xml +++ b/internal/guard/testdata/screens/preset-refused.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + The question - Preset + Preset @@ -150,7 +150,7 @@ - + Settings @@ -158,7 +158,7 @@ - Limit + Limit @@ -209,7 +209,7 @@ - Spread + Spread @@ -240,7 +240,7 @@ - Format + Format @@ -269,14 +269,14 @@ - + Output - Output directory + Output directory * @@ -312,7 +312,7 @@ - Seed + Seed * @@ -347,7 +347,7 @@ - + diff --git a/internal/guard/testdata/screens/preset.png b/internal/guard/testdata/screens/preset.png index 8eca313..ee40750 100644 Binary files a/internal/guard/testdata/screens/preset.png and b/internal/guard/testdata/screens/preset.png differ diff --git a/internal/guard/testdata/screens/preset.xml b/internal/guard/testdata/screens/preset.xml index 61a61c5..51a0d9b 100644 --- a/internal/guard/testdata/screens/preset.xml +++ b/internal/guard/testdata/screens/preset.xml @@ -1,7 +1,7 @@ - + @@ -57,14 +57,14 @@ - + The question - Preset + Preset @@ -162,7 +162,7 @@ - + Settings @@ -170,7 +170,7 @@ - Formats + Formats @@ -203,14 +203,14 @@ - + Output - Output directory + Output directory * @@ -246,7 +246,7 @@ - Seed + Seed * @@ -281,7 +281,7 @@ - + diff --git a/internal/guard/testdata/screens/recipe-contents.png b/internal/guard/testdata/screens/recipe-contents.png index 78573b5..4d91a54 100644 Binary files a/internal/guard/testdata/screens/recipe-contents.png and b/internal/guard/testdata/screens/recipe-contents.png differ diff --git a/internal/guard/testdata/screens/recipe-contents.xml b/internal/guard/testdata/screens/recipe-contents.xml index 880648f..68ac719 100644 --- a/internal/guard/testdata/screens/recipe-contents.xml +++ b/internal/guard/testdata/screens/recipe-contents.xml @@ -1,7 +1,7 @@ - + @@ -58,7 +58,7 @@ - + Build on a preset @@ -75,7 +75,7 @@ - Start from a preset + Start from a preset @@ -96,7 +96,7 @@ - + @@ -132,7 +132,7 @@ - Format + Format @@ -159,7 +159,7 @@ - Batch name + Batch name * @@ -192,7 +192,7 @@ - How many files + How many files @@ -229,7 +229,7 @@ - Size + Size * @@ -273,7 +273,7 @@ - File names + File names @@ -364,9 +364,9 @@ - Format - How many files - Size + Format + How many files + Size @@ -451,14 +451,14 @@ - + Output - Output directory + Output directory * @@ -494,7 +494,7 @@ - Manifest file name + Manifest file name @@ -525,7 +525,7 @@ - Seed + Seed @@ -556,7 +556,7 @@ - Label in each file + Label in each file @@ -587,7 +587,7 @@ - + diff --git a/internal/guard/testdata/screens/recipe-on-a-preset.png b/internal/guard/testdata/screens/recipe-on-a-preset.png index 0abb3d3..131f319 100644 Binary files a/internal/guard/testdata/screens/recipe-on-a-preset.png and b/internal/guard/testdata/screens/recipe-on-a-preset.png differ diff --git a/internal/guard/testdata/screens/recipe-on-a-preset.xml b/internal/guard/testdata/screens/recipe-on-a-preset.xml index a635275..8f6df54 100644 --- a/internal/guard/testdata/screens/recipe-on-a-preset.xml +++ b/internal/guard/testdata/screens/recipe-on-a-preset.xml @@ -1,7 +1,7 @@ - + @@ -58,7 +58,7 @@ - + Build on a preset @@ -75,7 +75,7 @@ - Start from a preset + Start from a preset @@ -93,7 +93,7 @@ - Preset to build on + Preset to build on @@ -120,7 +120,7 @@ - Formats + Formats @@ -155,7 +155,7 @@ - + @@ -196,7 +196,7 @@ - Format + Format @@ -223,7 +223,7 @@ - Batch name + Batch name * @@ -256,7 +256,7 @@ - How many files + How many files @@ -293,7 +293,7 @@ - Size + Size * @@ -337,7 +337,7 @@ - File names + File names @@ -425,14 +425,14 @@ - + Output - Output directory + Output directory * @@ -468,7 +468,7 @@ - Manifest file name + Manifest file name @@ -499,7 +499,7 @@ - Seed + Seed @@ -530,7 +530,7 @@ - Label in each file + Label in each file @@ -561,7 +561,7 @@ - + diff --git a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png index 39ad9c8..c240806 100644 Binary files a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png and b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png differ diff --git a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml index 830d085..b518f6c 100644 --- a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml +++ b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml @@ -1,7 +1,7 @@ - + @@ -58,7 +58,7 @@ - + Build on a preset @@ -75,7 +75,7 @@ - Start from a preset + Start from a preset @@ -96,7 +96,7 @@ - + @@ -137,7 +137,7 @@ - Format + Format @@ -164,7 +164,7 @@ - Batch name + Batch name * @@ -211,7 +211,7 @@ - How many files + How many files @@ -248,7 +248,7 @@ - Size + Size * @@ -305,7 +305,7 @@ - File names + File names @@ -390,7 +390,7 @@ - + @@ -431,7 +431,7 @@ - Format + Format @@ -458,7 +458,7 @@ - Batch name + Batch name * @@ -488,7 +488,7 @@ - How many files + How many files @@ -525,7 +525,7 @@ - Size + Size * @@ -566,7 +566,7 @@ - File names + File names @@ -654,14 +654,14 @@ - + Output - Output directory + Output directory * @@ -697,7 +697,7 @@ - Manifest file name + Manifest file name @@ -728,7 +728,7 @@ - Seed + Seed @@ -759,7 +759,7 @@ - Label in each file + Label in each file @@ -793,7 +793,7 @@ - + diff --git a/internal/guard/testdata/screens/recipe-refused.png b/internal/guard/testdata/screens/recipe-refused.png index 3cfe564..f6d4060 100644 Binary files a/internal/guard/testdata/screens/recipe-refused.png and b/internal/guard/testdata/screens/recipe-refused.png differ diff --git a/internal/guard/testdata/screens/recipe-refused.xml b/internal/guard/testdata/screens/recipe-refused.xml index 22874c4..f12cced 100644 --- a/internal/guard/testdata/screens/recipe-refused.xml +++ b/internal/guard/testdata/screens/recipe-refused.xml @@ -1,7 +1,7 @@ - + @@ -58,7 +58,7 @@ - + Build on a preset @@ -75,7 +75,7 @@ - Start from a preset + Start from a preset @@ -96,7 +96,7 @@ - + @@ -132,7 +132,7 @@ - Format + Format @@ -159,7 +159,7 @@ - Batch name + Batch name * @@ -206,7 +206,7 @@ - How many files + How many files @@ -243,7 +243,7 @@ - Size + Size * @@ -300,7 +300,7 @@ - File names + File names @@ -388,14 +388,14 @@ - + Output - Output directory + Output directory * @@ -431,7 +431,7 @@ - Manifest file name + Manifest file name @@ -462,7 +462,7 @@ - Seed + Seed @@ -493,7 +493,7 @@ - Label in each file + Label in each file @@ -527,7 +527,7 @@ - + diff --git a/internal/guard/testdata/screens/recipe-two-batches.png b/internal/guard/testdata/screens/recipe-two-batches.png index 831af43..84dccd5 100644 Binary files a/internal/guard/testdata/screens/recipe-two-batches.png and b/internal/guard/testdata/screens/recipe-two-batches.png differ diff --git a/internal/guard/testdata/screens/recipe-two-batches.xml b/internal/guard/testdata/screens/recipe-two-batches.xml index 504ca7a..002f432 100644 --- a/internal/guard/testdata/screens/recipe-two-batches.xml +++ b/internal/guard/testdata/screens/recipe-two-batches.xml @@ -1,7 +1,7 @@ - + @@ -58,7 +58,7 @@ - + Build on a preset @@ -75,7 +75,7 @@ - Start from a preset + Start from a preset @@ -96,7 +96,7 @@ - + @@ -137,7 +137,7 @@ - Format + Format @@ -164,7 +164,7 @@ - Batch name + Batch name * @@ -197,7 +197,7 @@ - How many files + How many files @@ -234,7 +234,7 @@ - Size + Size * @@ -278,7 +278,7 @@ - File names + File names @@ -363,7 +363,7 @@ - + @@ -404,7 +404,7 @@ - Format + Format @@ -431,7 +431,7 @@ - Batch name + Batch name * @@ -464,7 +464,7 @@ - How many files + How many files @@ -501,7 +501,7 @@ - Size + Size * @@ -545,7 +545,7 @@ - File names + File names @@ -633,14 +633,14 @@ - + Output - Output directory + Output directory * @@ -676,7 +676,7 @@ - Manifest file name + Manifest file name @@ -707,7 +707,7 @@ - Seed + Seed @@ -738,7 +738,7 @@ - Label in each file + Label in each file @@ -769,7 +769,7 @@ - + diff --git a/internal/guard/testdata/screens/recipe.png b/internal/guard/testdata/screens/recipe.png index 92299f9..97b8c99 100644 Binary files a/internal/guard/testdata/screens/recipe.png and b/internal/guard/testdata/screens/recipe.png differ diff --git a/internal/guard/testdata/screens/recipe.xml b/internal/guard/testdata/screens/recipe.xml index 79eab4f..b369ac6 100644 --- a/internal/guard/testdata/screens/recipe.xml +++ b/internal/guard/testdata/screens/recipe.xml @@ -1,7 +1,7 @@ - + @@ -58,7 +58,7 @@ - + Build on a preset @@ -75,7 +75,7 @@ - Start from a preset + Start from a preset @@ -96,7 +96,7 @@ - + @@ -132,7 +132,7 @@ - Format + Format @@ -159,7 +159,7 @@ - Batch name + Batch name * @@ -192,7 +192,7 @@ - How many files + How many files @@ -229,7 +229,7 @@ - Size + Size * @@ -273,7 +273,7 @@ - File names + File names @@ -361,14 +361,14 @@ - + Output - Output directory + Output directory * @@ -404,7 +404,7 @@ - Manifest file name + Manifest file name @@ -435,7 +435,7 @@ - Seed + Seed @@ -466,7 +466,7 @@ - Label in each file + Label in each file @@ -497,7 +497,7 @@ - + diff --git a/internal/gui/catalogue/catalogue.go b/internal/gui/catalogue/catalogue.go index eb276a2..6af4461 100644 --- a/internal/gui/catalogue/catalogue.go +++ b/internal/gui/catalogue/catalogue.go @@ -74,7 +74,7 @@ func Entries() []Entry { button(), chooser(), entry(), toggle(), segments(), openList(), tabs(), fields(), propertyField(), byteCount(), tips(), errorArea(), progress(), folding(), - textRanks(), structure(), + textRanks(), structure(), swatch(), } } diff --git a/internal/gui/catalogue/palette.go b/internal/gui/catalogue/palette.go new file mode 100644 index 0000000..9ded026 --- /dev/null +++ b/internal/gui/catalogue/palette.go @@ -0,0 +1,253 @@ +package catalogue + +import ( + "fmt" + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + + "github.com/donislawdev/TestingFilesGenerator/internal/gui/parts" +) + +// The palette, as a page somebody can open. +// +// Every row is built from parts.PaletteNames rather than from a list written +// out here, so a colour added to the palette appears on this page by itself. +// The reading beside each one is computed when the page is built, not copied +// from a document: a number in prose is a claim about the day it was written, +// and this one is a claim about the binary it is printed by. +// +// Both variants are here. The light one is computed, measured by the same +// guards and NOT installed - the owner's decision of 2026-08-11 is that this +// program has one look - and showing it is the only way anybody ever sees the +// half of the work that has never been on a screen. + +// paletteSections is the palette as the catalogue shows it: one section per +// variant, each grouped the way the colours are read. +func paletteSections() []fyne.CanvasObject { + return []fyne.CanvasObject{ + paletteSection("Palette, as this window draws it", theme.VariantDark, + "Every colour the window paints with, measured against what it is drawn on."), + paletteSection("Palette, light - computed and not installed", theme.VariantLight, + "Worked out and guarded to the same thresholds. The window does not use it: this program has one look."), + } +} + +func paletteSection(title string, variant fyne.ThemeVariant, sentence string) fyne.CanvasObject { + rows := []fyne.CanvasObject{parts.Caption(sentence)} + group := "" + for _, name := range parts.PaletteNames() { + role, known := palettePlan[name] + if !known { + // A colour nobody has said how to read. It still shows, because + // the alternative is a page that quietly omits it. + role = paletteRole{group: unplaced, what: "no reading written for this one yet"} + } + if role.group != group { + group = role.group + rows = append(rows, parts.Subheading(group)) + } + rows = append(rows, paletteRow(name, role, variant)) + } + return parts.Section(title, rows...) +} + +// paletteRow is one colour: the square, the name it is asked for by, the value +// it holds and the number that says whether it is doing its job. +func paletteRow(name fyne.ThemeColorName, role paletteRole, variant fyne.ThemeVariant) fyne.CanvasObject { + return container.NewHBox( + parts.Swatch(shown(name, role, variant)), + parts.Column(parts.GapInline, + parts.Caption(string(name)+" - "+role.what), + parts.Caption(hexOf(parts.PaletteColour(name, variant))+reading(name, role, variant))), + ) +} + +// shown is the colour to put in the square, which for anything translucent is +// what it comes to over the surface it lands on rather than the value itself. +func shown(name fyne.ThemeColorName, role paletteRole, variant fyne.ThemeVariant) color.Color { + if role.against == "" { + return parts.PaletteColour(name, variant) + } + return parts.PaletteSeen(name, parts.PaletteColour(role.against, variant), variant) +} + +// reading is the measurement that decides whether this colour works: a +// contrast ratio for anything read, a distance in L* for a surface, and for +// something the toolkit blends, both what it comes to and how far it moved. +func reading(name fyne.ThemeColorName, role paletteRole, variant fyne.ThemeVariant) string { + if role.against == "" { + return "" + } + against := parts.PaletteColour(role.against, variant) + switch role.measure { + case byNothing: + // A colour that is not measured against anything names no surface + // either, so this is unreachable rather than empty - and it is written + // out because the linter asks every switch on this type to say what it + // does with every value, which is the correct thing to ask. + return "" + case byContrast: + return fmt.Sprintf(" - %.2f:1 on %s", parts.Contrast(parts.PaletteColour(name, variant), against), role.on) + case byStep: + step := parts.Lightness(parts.PaletteColour(name, variant)) - parts.Lightness(against) + return fmt.Sprintf(" - %.1f L*, %+.1f from %s", + parts.Lightness(parts.PaletteColour(name, variant)), step, role.on) + case byMove: + seen := parts.PaletteSeen(name, against, variant) + return fmt.Sprintf(" - over %s it comes to %s, %.1f L*, a move of %+.1f", + role.on, hexOf(seen), parts.Lightness(seen), parts.Lightness(seen)-parts.Lightness(against)) + } + return "" +} + +// hexOf is the value as it stands in the palette, alpha and all. +// +// The alpha is printed rather than dropped, and the row beside it says what +// the colour comes to over the surface it lands on, because those are two +// different facts and this palette has been bitten by treating them as one: +// the toolkit BLENDS half of these names rather than painting them, so the +// raw value of the hover is a colour that has never been on the screen and +// the composited one is not what anybody can edit. +func hexOf(c color.Color) string { + r, g, b, a := c.RGBA() + switch { + case a == 0: + return "nothing - drawn transparent on purpose" + case a < 0xFFFF: + return fmt.Sprintf("#%02X%02X%02X at 0x%02X", r*0xFFFF/a>>8, g*0xFFFF/a>>8, b*0xFFFF/a>>8, a>>8) + } + return fmt.Sprintf("#%02X%02X%02X", r>>8, g>>8, b>>8) +} + +type measure int + +const ( + byNothing measure = iota + byContrast + byStep + byMove +) + +type paletteRole struct { + group string + what string + against fyne.ThemeColorName + on string + measure measure +} + +const ( + ladder = "The ladder of surfaces" + inks = "The inks" + meaning = "What carries meaning" + pointer = "What the pointer, a press and the keyboard do" + onFace = "What is written on a filled face" + unplaced = "Not placed yet" +) + +// How each colour is read. The group and the sentence are for a person, and +// the pair at the end is the whole point: a colour is never measured on its +// own, it is measured against the thing it is drawn on, and getting THAT wrong +// is how this palette shipped a button nobody could read the word on. +var palettePlan = map[fyne.ThemeColorName]paletteRole{ + theme.ColorNameBackground: {ladder, "the page the window stands on", "", "", byNothing}, + parts.ColorNamePanel: {ladder, "the surface a section is drawn on", + theme.ColorNameBackground, "the page", byStep}, + theme.ColorNameInputBackground: {ladder, "the fill of a box to type in", + parts.ColorNamePanel, "the panel", byStep}, + theme.ColorNameButton: {ladder, "the face of a button that is not the main one", + theme.ColorNameInputBackground, "a box to type in", byStep}, + theme.ColorNameSeparator: {ladder, "a line that separates and says nothing else", + parts.ColorNamePanel, "the panel", byStep}, + theme.ColorNameMenuBackground: {ladder, "what an open list floats on", + theme.ColorNameInputBackground, "a box to type in", byStep}, + theme.ColorNameInputBorder: {ladder, "the edge that says where the typing goes", + theme.ColorNameInputBackground, "its own fill", byStep}, + + // Each ink is measured against the LIGHTEST surface it is actually drawn + // on, which for three of these four is not the panel. A ratio taken + // against the wrong surface is the one mistake this page exists to stop + // somebody making, and it was made here first: measured 2026-09-22, the + // hint in an empty box reads 5.56:1 against a panel and 4.52:1 against the + // box it is really in, which is 0.02 over the 4.5 a reader needs. The + // comfortable number was the wrong number. + // + // The surface each one is drawn on is read from the code rather than + // assumed: a list row draws its words in Foreground on the floating + // surface (parts/listrow.go), a value and a hint sit inside a box, and a + // field's name stands on the panel beside it. + theme.ColorNameForeground: {inks, "a value, and everything read at rest", + theme.ColorNameMenuBackground, "an open list", byContrast}, + parts.ColorNameLabel: {inks, "the name of a field, a step quieter than its value", + parts.ColorNamePanel, "a panel", byContrast}, + theme.ColorNameDisabled: {inks, "a value in a box switched off for the length of a run", + theme.ColorNameInputBackground, "a box to type in", byContrast}, + theme.ColorNamePlaceHolder: {inks, "a hint in a box nobody has typed in", + theme.ColorNameInputBackground, "a box to type in", byContrast}, + + theme.ColorNamePrimary: {meaning, "the action a screen is for, and the line round a focused control", + theme.ColorNameBackground, "the page", byContrast}, + theme.ColorNameHyperlink: {meaning, "a link, the same blue as the action", + theme.ColorNameBackground, "the page", byContrast}, + theme.ColorNameError: {meaning, "a refusal, and a file that failed", + theme.ColorNameBackground, "the page", byContrast}, + theme.ColorNameSuccess: {meaning, "a run that wrote everything it promised", + theme.ColorNameBackground, "the page", byContrast}, + theme.ColorNameWarning: {meaning, "a run that wrote files and skipped some", + theme.ColorNameBackground, "the page", byContrast}, + theme.ColorNameSelection: {meaning, "the row or the segment somebody chose", + theme.ColorNameBackground, "the page", byStep}, + + theme.ColorNameHover: {pointer, "what the pointer does to a dark face", + theme.ColorNameBackground, "the page", byMove}, + theme.ColorNamePressed: {pointer, "what a press does to a dark face", + theme.ColorNameButton, "a button's face", byMove}, + parts.ColorNameLift: {pointer, "what the pointer does to the filled main button", + theme.ColorNamePrimary, "the main button", byMove}, + parts.ColorNameShade: {pointer, "what a press does to the filled main button", + theme.ColorNamePrimary, "the main button", byMove}, + theme.ColorNameFocus: {pointer, "the wash the toolkit lays over a control the keyboard is in", + theme.ColorNameInputBackground, "a box to type in", byMove}, + parts.ColorNameTipShade: {pointer, "the shade an explanation casts on the form under it", + parts.ColorNamePanel, "a panel", byMove}, + theme.ColorNameShadow: {pointer, "what a popup casts - nothing here, on purpose", "", "", byNothing}, + + theme.ColorNameForegroundOnPrimary: {onFace, "the word on the main button", + theme.ColorNamePrimary, "the main button", byContrast}, + theme.ColorNameForegroundOnError: {onFace, "what is written on a filled red", + theme.ColorNameError, "a filled red", byContrast}, + theme.ColorNameForegroundOnSuccess: {onFace, "what is written on a filled green", + theme.ColorNameSuccess, "a filled green", byContrast}, + theme.ColorNameForegroundOnWarning: {onFace, "what is written on a filled amber", + theme.ColorNameWarning, "a filled amber", byContrast}, +} + +// swatch is the square itself, in the catalogue like every other part. +// +// The states are the ones that decide whether it works: the page colour, which +// is a black square on a black screen and is only a square at all because of +// the line round it, and a colour the toolkit blends, which is transparent and +// would be an empty frame if the page did not lay it over a surface first. +func swatch() Entry { + return Entry{Name: "Swatch", Natural: true, States: []State{ + {"a surface near the bottom of the ladder", func() fyne.CanvasObject { + return parts.Swatch(parts.PaletteColour(theme.ColorNameBackground, theme.VariantDark)) + }}, + {"a surface near the top of it", func() fyne.CanvasObject { + return parts.Swatch(parts.PaletteColour(theme.ColorNameInputBorder, theme.VariantDark)) + }}, + {"a colour that carries meaning", func() fyne.CanvasObject { + return parts.Swatch(parts.PaletteColour(theme.ColorNamePrimary, theme.VariantDark)) + }}, + {"an ink, which is nearly white", func() fyne.CanvasObject { + return parts.Swatch(parts.PaletteColour(theme.ColorNameForeground, theme.VariantDark)) + }}, + {"something the toolkit blends, laid over a panel first", func() fyne.CanvasObject { + return parts.Swatch(parts.PaletteSeen(theme.ColorNameHover, + parts.PaletteColour(parts.ColorNamePanel, theme.VariantDark), theme.VariantDark)) + }}, + }} +} diff --git a/internal/gui/catalogue/screen.go b/internal/gui/catalogue/screen.go index 97cfa96..66c0e43 100644 --- a/internal/gui/catalogue/screen.go +++ b/internal/gui/catalogue/screen.go @@ -28,6 +28,10 @@ func Page() fyne.CanvasObject { for _, e := range entries { sections = append(sections, section(e)) } + // The palette stands before the two sections that say what is left out, + // because it is what every control above it is painted with rather than + // another thing in the list. + sections = append(sections, paletteSections()...) sections = append(sections, parts.Section("Not drawn, and why", parts.Bullets(sentences(NotDrawn()))), parts.Section("Layout only, and why", parts.Bullets(sentences(LayoutOnly()))), diff --git a/internal/gui/parts/palette.go b/internal/gui/parts/palette.go new file mode 100644 index 0000000..585aa17 --- /dev/null +++ b/internal/gui/parts/palette.go @@ -0,0 +1,165 @@ +package parts + +import ( + "image/color" + "math" + "sort" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/theme" +) + +// What this file adds to theme.go: a way to LOOK at the palette. +// +// The values have been measured, guarded and written about since 2026-08-11 +// and until 2026-09-22 there was nowhere in the running program to see them. +// That is the same shape as the defect the catalogue of parts was built to +// end - a control existed in four states and only three were ever drawn +// anywhere - one floor down. A palette nobody can open is a palette argued +// about from memory. + +// Swatch is a square of one colour, for the palette page of the catalogue. +// +// It wears a line round it, and that line is not decoration: half of this +// palette is within a few L* of the page it would be drawn on, so a swatch of +// the panel colour on a panel is an invisible square in a row that claims to +// show a colour. The line is the one a divider uses, so the mark is one this +// window already makes rather than a second kind of edge. +func Swatch(c color.Color) fyne.CanvasObject { + chip := canvas.NewRectangle(c) + chip.CornerRadius = RadiusMark + chip.StrokeColor = PaletteColour(theme.ColorNameSeparator, theme.VariantDark) + chip.StrokeWidth = edgeWidth + chip.SetMinSize(fyne.NewSize(SwatchSide, SwatchSide)) + return chip +} + +// PaletteNames is every colour this palette answers for, in the order a person +// reads them: the ladder from the page up to the edge of a field, then the +// inks, then what carries meaning, then what the pointer and the keyboard do, +// and last what is written on a filled face. +// +// Read off the palette itself rather than listed here, which is the whole +// point. A list typed out beside a map is a list that goes stale the first +// time somebody adds a colour and does not think of the page that shows it - +// this project has the receipts on that, and they are in CLAUDE.md. A name +// nobody has ranked still comes back, at the end, where it is obvious. +func PaletteNames() []fyne.ThemeColorName { + names := make([]fyne.ThemeColorName, 0, len(darkColours)) + for name := range darkColours { + names = append(names, name) + } + sort.Slice(names, func(i, j int) bool { + oneRank, oneKnown := paletteOrder[names[i]] + twoRank, twoKnown := paletteOrder[names[j]] + if oneKnown != twoKnown { + return oneKnown + } + if oneRank != twoRank { + return oneRank < twoRank + } + return names[i] < names[j] + }) + return names +} + +// PaletteRanked says whether a colour has a place in the order above. It is +// false for a colour somebody added without deciding where it is read, which +// is a question for a person rather than something to guess at here. +func PaletteRanked(name fyne.ThemeColorName) bool { + _, ok := paletteOrder[name] + return ok +} + +// PaletteSeen is what a colour LOOKS like, which is a different question from +// what it is - and the difference is the one this palette has been caught by +// twice. The toolkit blends the hover, the press and the focus over whatever +// is underneath rather than painting them, so the raw value of the hover is a +// colour nobody has ever seen on this screen. Given the surface it lands on, +// this returns what is drawn. +func PaletteSeen(name fyne.ThemeColorName, over color.Color, variant fyne.ThemeVariant) color.Color { + c := PaletteColour(name, variant) + if _, _, _, alpha := c.RGBA(); alpha < 0xFFFF { + return blended(over, c) + } + return c +} + +// Contrast is the WCAG ratio between two colours, from 1 to 21 - the measure +// for anything that is read, at 4.5, and for anything carrying state, at 3.0. +func Contrast(one, two color.Color) float64 { + a, b := relativeLuminance(one), relativeLuminance(two) + if a < b { + a, b = b, a + } + return (a + 0.05) / (b + 0.05) +} + +// Lightness is CIE L*, which is perceptually even - so one number means the +// same thing against a dark background and a light one. It is the measure for +// a surface, where a contrast ratio would compress against a light page and +// quietly mean something different in each variant. +func Lightness(c color.Color) float64 { + y := relativeLuminance(c) + if y > 0.008856 { + return 116*math.Cbrt(y) - 16 + } + return 903.3 * y +} + +func relativeLuminance(c color.Color) float64 { + r, g, b, _ := c.RGBA() + straight := func(v uint32) float64 { + f := float64(v) / 65535 + if f <= 0.04045 { + return f / 12.92 + } + return math.Pow((f+0.055)/1.055, 2.4) + } + return 0.2126*straight(r) + 0.7152*straight(g) + 0.0722*straight(b) +} + +// The order colours are read in. Ranks rather than a sorted list of names, so +// that adding a colour is a decision about WHERE IT BELONGS and not about +// where it lands alphabetically. +var paletteOrder = map[fyne.ThemeColorName]int{ + // The ladder, bottom to top. + theme.ColorNameBackground: 10, + ColorNamePanel: 11, + theme.ColorNameInputBackground: 12, + theme.ColorNameButton: 13, + theme.ColorNameSeparator: 14, + theme.ColorNameMenuBackground: 15, + theme.ColorNameInputBorder: 16, + + // The inks, brightest first, which is the order they are met in a field. + theme.ColorNameForeground: 20, + ColorNameLabel: 21, + theme.ColorNameDisabled: 22, + theme.ColorNamePlaceHolder: 23, + + // What carries meaning. + theme.ColorNamePrimary: 30, + theme.ColorNameHyperlink: 31, + theme.ColorNameError: 32, + theme.ColorNameSuccess: 33, + theme.ColorNameWarning: 34, + theme.ColorNameSelection: 35, + + // What the pointer, the press and the keyboard do - every one of these is + // blended rather than painted. + theme.ColorNameHover: 40, + theme.ColorNamePressed: 41, + ColorNameLift: 42, + ColorNameShade: 43, + theme.ColorNameFocus: 44, + ColorNameTipShade: 45, + theme.ColorNameShadow: 46, + + // What is written on a filled face. + theme.ColorNameForegroundOnPrimary: 50, + theme.ColorNameForegroundOnError: 51, + theme.ColorNameForegroundOnSuccess: 52, + theme.ColorNameForegroundOnWarning: 53, +} diff --git a/internal/gui/parts/theme.go b/internal/gui/parts/theme.go index 01cc035..85fe5d4 100644 --- a/internal/gui/parts/theme.go +++ b/internal/gui/parts/theme.go @@ -21,6 +21,41 @@ import ( // L* rather than at a contrast ratio at all. A single ratio threshold on // subtle surfaces measures something different in each variant, because the // ratio compresses against a light background. +// +// Since 2026-09-22 every value below comes from a RULE rather than from being +// picked one at a time, and that is the difference between a palette and a +// table of colours that each passed a threshold. Measured on 2026-09-22, on +// the palette as it stood: the surfaces sat at hue 291, the inks at 251 to +// 262, and the page and the main ink were pure neutral - three different +// greys in one window. The steps between surfaces were 5.9, 6.5, 1.4, 0.9 and +// 4.7 in L*, so the separator, the menu and a button's face were one colour in +// three names. The four colours carrying meaning had chroma 35.9, 53.8, 55.0 +// and 64.4, which is why the accent was the quietest thing on a screen it is +// supposed to lead. +// +// The rule has four parts and TestThePaletteIsTheOneTheRuleProduces holds the +// table to it: +// +// - One hue for every neutral, surface and ink alike. A window is one +// material. +// - Chroma on one curve, Cmax * sin(pi * L / 100), so the tint is strongest +// in the middle of the ladder and fades at both ends rather than being +// chosen per colour. +// - Lightness in even steps: 11.3, 17.7, 24.2, 30.8, 37.2 and 43.0 in the +// dark variant, which is a step of about 6.4. A line is not a surface, so +// it takes the rank of the face beside it rather than inventing one - the +// separator and a button's face are deliberately the same value, and a one +// pixel line arrives darker anyway because it is anti-aliased. +// - The accent and the three status colours share one lightness (70.1) and +// one SHARE of the chroma their hue can reach in sRGB (0.85), not one +// chroma. A common chroma is not available: at that lightness red reaches +// 49.5 and green 80.1, and asking both for 45 left the amber looking like +// mud. Measured 2026-09-22. +// +// The generator is tools/probes/palette/design.py and the reader that prints +// every number this file's prose quotes is tools/probes/palette/measure.py. +// Neither is needed to build the window: they are how the values were reached +// and how a sentence here is checked against the file it stands in. // ColorNamePanel is the surface a section is drawn on. // // A name of ours rather than one of the toolkit's, because the toolkit has @@ -52,8 +87,13 @@ const ColorNameTipShade fyne.ThemeColorName = "tipshade" // seen. To move the primary face by the 10 L* this palette calls noticeable // takes white at 0x66, and that same alpha on a dark face would move it by // twenty five. One name cannot be right for both faces, so the light face has -// its own two. Measured on 2026-09-15: 83.0 L* under the pointer and 58.5 -// pressed, against 71.9 at rest, with the ink on every one of them above 4.5. +// its own two. +// +// Measured on 2026-09-22, on the ladder this palette now stands on: the +// primary face is 70.1 L* at rest, 81.5 under the pointer and 57.0 pressed, +// with the ink on it at 10.67:1 and 4.96:1 - both above the 4.5 a reader +// needs, and the pressed one is the tighter of the two because a press is +// what darkens the face the ink was chosen for. const ( ColorNameLift fyne.ThemeColorName = "lift" ColorNameShade fyne.ThemeColorName = "shade" @@ -61,9 +101,9 @@ const ( var ( darkColours = map[fyne.ThemeColorName]color.Color{ - theme.ColorNameBackground: hex(0x1E, 0x1E, 0x1E), - theme.ColorNameForeground: hex(0xE6, 0xE6, 0xE6), - theme.ColorNamePlaceHolder: hex(0x9D, 0xA3, 0xA8), + theme.ColorNameBackground: hex(0x1E, 0x1E, 0x20), + theme.ColorNameForeground: hex(0xE6, 0xE6, 0xE8), + theme.ColorNamePlaceHolder: hex(0xA2, 0xA2, 0xA9), // A step of its own between a value and a hint, since 2026-08-20. // // It was the placeholder colour exactly, so a box switched off for the @@ -74,7 +114,7 @@ var ( // the width box read #9DA3A8 for "worked out from the size". // // Three steps now, and the gaps are what was picked rather than the - // values: 91.3, 80.3 and 66.7 in L*, which is 11.0 and 13.6 apart. The + // values: 91.3, 80.4 and 66.8 in L*, which is 10.9 and 13.6 apart. The // palette's own yardstick for "noticeable" is 10. // // Brighter rather than dimmer, because a disabled value is content @@ -82,12 +122,12 @@ var ( // made the one thing a person wants to re-read during a run the hardest // thing on the screen to read. A button loses its fill when it is // disabled, so it does not need the text to carry the whole message. - theme.ColorNameDisabled: hex(0xC2, 0xC8, 0xCD), - theme.ColorNameError: hex(0xF1, 0x70, 0x7A), - theme.ColorNameSuccess: hex(0x6F, 0xCF, 0x7F), - theme.ColorNameWarning: hex(0xE8, 0xB3, 0x3E), - theme.ColorNamePrimary: hex(0x6F, 0xB7, 0xF0), - theme.ColorNameHyperlink: hex(0x6F, 0xB7, 0xF0), + theme.ColorNameDisabled: hex(0xC7, 0xC7, 0xCC), + theme.ColorNameError: hex(0xF4, 0x8F, 0x8D), + theme.ColorNameSuccess: hex(0x42, 0xC2, 0x60), + theme.ColorNameWarning: hex(0xDA, 0xA1, 0x31), + theme.ColorNamePrimary: hex(0x5A, 0xB3, 0xF2), + theme.ColorNameHyperlink: hex(0x5A, 0xB3, 0xF2), // Translucent, for the reason the hover below it is - and this one was // left opaque when that was corrected on 2026-08-11, so the same defect // stayed on the screen in a second place for a day. @@ -103,12 +143,15 @@ var ( // // The value is 0x66 rather than the toolkit's own 0x2a because ours has // to work over an input box rather than over the page: blue at 0x66 - // over #2E2E30 comes out at #3A5A7D, which leaves the value on it at - // 5.73:1. What it can no longer do is say WHERE the keyboard is - a + // over the input box comes out at #226486, which leaves the value on it + // at 5.21:1 - measured 2026-09-22, and the lightness of this blue is + // SEARCHED for that number rather than typed, because it is the one + // value in the palette pinned from both sides at once. What it can no + // longer do is say WHERE the keyboard is - a // wash that text survives is a wash nothing else can see either, and // that is arithmetic rather than a compromise. See parts.Ring, which is // what carries the state instead. - theme.ColorNameFocus: overlay(0x4C, 0x9D, 0xF0, 0x66), + theme.ColorNameFocus: overlay(0x00, 0xA5, 0xF0, 0x66), // Translucent, and that is the fix for a defect somebody saw rather // than a preference. The toolkit does not paint this colour, it BLENDS // it over whatever is underneath - so an opaque grey replaced the blue @@ -121,13 +164,15 @@ var ( // as the background of a row and the toolkit uses it on anything. theme.ColorNameHover: overlay(0xFF, 0xFF, 0xFF, 0x22), // What a press does to a dark face: more of the same white, because a - // press has to be told from the hover it follows and black over - // #2A2A2D moves it by 3.9 L*, which nobody sees. Measured 2026-09-15. + // press has to be told from the hover it follows and black over a + // surface this dark moves it by 3.9 L*, which nobody sees - measured + // 2026-09-15 on the panel colour of the day. On today's ladder the two + // read 40.9 and 49.8 L* on a button's face, against 30.8 at rest. theme.ColorNamePressed: overlay(0xFF, 0xFF, 0xFF, 0x40), // The two for the light face, see ColorNameLift. ColorNameLift: overlay(0xFF, 0xFF, 0xFF, 0x66), ColorNameShade: overlay(0x00, 0x00, 0x00, 0x33), - theme.ColorNameSelection: hex(0x2C, 0x4A, 0x6B), + theme.ColorNameSelection: hex(0x22, 0x4B, 0x69), // A box to type in has to be findable without reading a word, and until // 2026-08-23 it was not. Measured on the palette as it stood: a field // sat 8 of 255 above the panel it is drawn on, a ratio of 1.11, and its @@ -135,18 +180,20 @@ var ( // surfaces within sixteen steps of each other, so the eye had nothing // to count fields by. // - // The step is 18 now against the panel and 29 from fill to border, - // which is 2.3 and 2.6 times what it was. Deliberately still a raised - // surface rather than a sunken one: a well reads well on a card and + // The step is one rank of the ladder against the panel - 6.5 L*, which + // the guard reads as a third of the room between page and field - and + // the border stands 18.9 L* off the fill, two ranks up. Deliberately + // still a raised surface rather than a sunken one: a well reads well on + // a card and // disappears where there is no card, and a field measured at 1.06 // against the window background would be worse than what it replaced. - theme.ColorNameInputBackground: hex(0x38, 0x38, 0x3D), - theme.ColorNameInputBorder: hex(0x55, 0x55, 0x5C), + theme.ColorNameInputBackground: hex(0x39, 0x39, 0x3F), + theme.ColorNameInputBorder: hex(0x65, 0x65, 0x6D), // A button is no longer the same colour as a box to type in. It was, // exactly, and that is half of why a menu and a field were one object // with an arrow on the end of it - see the note on Chooser. - theme.ColorNameButton: hex(0x4A, 0x4A, 0x52), - ColorNameLabel: hex(0xCF, 0xD3, 0xD8), + theme.ColorNameButton: hex(0x48, 0x48, 0x4E), + ColorNameLabel: hex(0xD3, 0xD3, 0xD7), // A menu floats over everything, so it is the LIGHTEST surface rather // than another one at the height of an input box. @@ -173,11 +220,19 @@ var ( // REQUIRED, and reading the prose for the threshold got it wrong by a // margin no one would see by looking. // - // #48484F is 30.8 L*, which puts it 7.1 above an input and 15.6 above - // the panel. The stack reads page 11.3, panel 15.2, input 23.7, menu - // 30.8: each one told from the one under it, and the one that floats - // furthest from all of them. - theme.ColorNameMenuBackground: hex(0x48, 0x48, 0x4F), + // And the sentence at the top of this note was not true until + // 2026-09-22, which is the whole argument for a ladder. A button's face + // arrived later at #4A4A52, 31.7 L*, while the menu stood at 30.8 - so + // the LIGHTEST surface was a button, the thing that floats over + // everything was under it, and every guard was green because no guard + // compares those two. Nobody added a wrong value: two right values were + // chosen a month apart against different neighbours. + // + // #57575F is 37.2 L*, one rank clear of a button's face and 13.1 above + // an input. The stack reads page 11.3, panel 17.7, input 24.2, face and + // line 30.8, menu 37.2, the edge of a field 43.0: each one told from + // the one under it, and the one that floats furthest from all of them. + theme.ColorNameMenuBackground: hex(0x57, 0x57, 0x5F), // And what it casts, which is the other half of floating. The toolkit // draws a shadow under every popup and asks the theme for its colour - @@ -216,8 +271,10 @@ var ( // taking the surface value section 8.2 already records. That value is // what an input box is - a panel painted with it would swallow every // field standing on it. Three surfaces stack here and each has to be - // told from the one under it: page 11.3, panel 17.2, input 23.7 in L*, - // which is +5.9 and +6.5. + // told from the one under it: page 11.3, panel 17.7, input 24.2 in L*, + // which is +6.3 and +6.5 - two ranks of the ladder, and the ladder is + // where those numbers come from rather than from this stack being + // solved on its own. // // It used to be 15.2, and the line round the edge did the work the fill // could not - four L* is a surface you sense rather than see. That line @@ -231,20 +288,27 @@ var ( // TestASectionDrawsItsOwnSurface holds the fill against the page with // no edge to fall back on. // - // The colour is 29.4 L* and what gets drawn is not. Measured off the - // render on 2026-08-12: a one pixel stroke lands between pixels and is - // anti-aliased, so the brightest pixel of the edge comes out at 22.3 - - // which is +7.1 from the panel and +11.0 from the page. It reads, and - // it reads because of the gap to the page rather than the one to the - // panel. Worth keeping straight, because the number in a palette is a - // claim about a colour and only the render is a claim about a line. + // The colour is 30.8 L* and what gets drawn is not. Measured off the + // render on 2026-08-12, when this colour was 29.4: a one pixel stroke + // lands between pixels and is anti-aliased, so the brightest pixel of + // the edge came out at 22.3 - about three quarters of the value it was + // given. It reads, and it reads because of the gap to the page rather + // than the one to the panel. Worth keeping straight, because the number + // in a palette is a claim about a colour and only the render is a claim + // about a line. + // + // That is also why this line shares the rank of a button's face instead + // of taking one of its own. The first cut of the ladder gave it 29.0 + // beside a face at 30.8 - 1.7 L* apart, which is the very crowding at + // the top of the old palette this rule was written to end. A rank is + // for a surface. A line borrows one. // // It is no longer the panel's edge - see the note above - so the only // thing drawn with it now is parts.Divider. The measurement stays // because it is about a one pixel line on this page, which is what a // divider is. - theme.ColorNameSeparator: hex(0x45, 0x45, 0x49), - ColorNamePanel: hex(0x2A, 0x2A, 0x2D), + theme.ColorNameSeparator: hex(0x48, 0x48, 0x4E), + ColorNamePanel: hex(0x2B, 0x2B, 0x2F), // What is written ON one of those colours, which is a different // question from what they contrast with. Section 8 computed them as @@ -264,20 +328,20 @@ var ( lightColours = map[fyne.ThemeColorName]color.Color{ theme.ColorNameBackground: hex(0xFF, 0xFF, 0xFF), - theme.ColorNameForeground: hex(0x1A, 0x1A, 0x1A), - theme.ColorNamePlaceHolder: hex(0x59, 0x59, 0x59), + theme.ColorNameForeground: hex(0x1A, 0x1A, 0x1C), + theme.ColorNamePlaceHolder: hex(0x58, 0x58, 0x60), // The same three steps the other way up: on a white page "closer to - // the value" means darker. 9.3, 25.8 and 37.8 in L*, 16.5 and 12.1 + // the value" means darker. 9.3, 25.9 and 37.7 in L*, 16.6 and 11.8 // apart. - theme.ColorNameDisabled: hex(0x3D, 0x3D, 0x3D), - theme.ColorNameError: hex(0xB3, 0x12, 0x1F), - theme.ColorNameSuccess: hex(0x10, 0x6B, 0x2E), - theme.ColorNameWarning: hex(0x8A, 0x5A, 0x00), - theme.ColorNamePrimary: hex(0x0F, 0x5F, 0xA8), - theme.ColorNameHyperlink: hex(0x0F, 0x5F, 0xA8), + theme.ColorNameDisabled: hex(0x3D, 0x3D, 0x42), + theme.ColorNameError: hex(0xBB, 0x29, 0x3B), + theme.ColorNameSuccess: hex(0x23, 0x71, 0x35), + theme.ColorNameWarning: hex(0x80, 0x5D, 0x18), + theme.ColorNamePrimary: hex(0x28, 0x68, 0x93), + theme.ColorNameHyperlink: hex(0x28, 0x68, 0x93), // The same correction as the dark variant above, at the same alpha: // this is blended over whatever it lands on rather than painted. - theme.ColorNameFocus: overlay(0x0F, 0x62, 0xFE, 0x66), + theme.ColorNameFocus: overlay(0x00, 0x7F, 0xBA, 0x66), // Black rather than white here: this page is white, so its hover is // DARKER than what is under it. 0x20 over white comes out at #DFDFDF, // which is what section 8.3 measured. @@ -288,15 +352,15 @@ var ( theme.ColorNamePressed: overlay(0x00, 0x00, 0x00, 0x33), ColorNameLift: overlay(0xFF, 0xFF, 0xFF, 0x66), ColorNameShade: overlay(0x00, 0x00, 0x00, 0x33), - theme.ColorNameSelection: hex(0xCF, 0xE4, 0xF7), - // The same step, worked out the same way against a white page. Here a + theme.ColorNameSelection: hex(0xC1, 0xE0, 0xFF), + // The same rank, worked out the same way against a white page. Here a // field is the sunken one - on white there is nowhere lighter to go - - // so it sits below the panel rather than above it, 16 of 255 down - // instead of the 8 it was, with a border 63 below the fill. - theme.ColorNameInputBackground: hex(0xE7, 0xE7, 0xEA), - theme.ColorNameInputBorder: hex(0xA8, 0xA8, 0xB0), - theme.ColorNameButton: hex(0xEF, 0xEF, 0xEF), - ColorNameLabel: hex(0x44, 0x44, 0x48), + // so it sits below the panel rather than above it, one rank down, with + // a border two ranks further at 18.0 L* below the fill. + theme.ColorNameInputBackground: hex(0xDC, 0xDC, 0xE0), + theme.ColorNameInputBorder: hex(0xAB, 0xAA, 0xB2), + theme.ColorNameButton: hex(0xCC, 0xCB, 0xD1), + ColorNameLabel: hex(0x43, 0x43, 0x49), // The same reasoning the other way up: on a white page a floating // surface is the lightest thing there is, so it is white and the shadow // is what separates it. @@ -304,13 +368,15 @@ var ( theme.ColorNameShadow: overlay(0x00, 0x00, 0x00, 0x40), ColorNameTipShade: overlay(0x00, 0x00, 0x00, 0x40), - // The same two, worked out the same way against a white page. The gap - // between page and input is narrower here - 5.6 L* against 7.7 - so the - // panel splits it at 2.8 either side, and the line again carries the - // edge at 11.5 L*. Computed rather than installed, like the rest of this - // palette. - theme.ColorNameSeparator: hex(0xD6, 0xD6, 0xD9), - ColorNamePanel: hex(0xF4, 0xF4, 0xF5), + // The same two, worked out the same way against a white page. White + // leaves 30 L* down to the edge of a field where the dark variant has + // 31.7 up to it, so the rank here is 6.0 rather than 6.4 - the step is + // even within a variant, which is the rule, and the two variants are + // not asked to share a number they do not have the room for. The line + // takes the rank of a button's face, exactly as it does above. + // Computed rather than installed, like the rest of this palette. + theme.ColorNameSeparator: hex(0xCC, 0xCB, 0xD1), + ColorNamePanel: hex(0xEE, 0xEE, 0xEF), // The other way round here, for the same reason: these are dark enough // to read on a white page, so what is written on them is white. diff --git a/internal/gui/parts/tokens.go b/internal/gui/parts/tokens.go index 9831498..7c55439 100644 --- a/internal/gui/parts/tokens.go +++ b/internal/gui/parts/tokens.go @@ -185,6 +185,11 @@ const ( // glyph itself is the toolkit's inline icon, 20, and the square keeps two // pixels round it, which is what makes it a target and not a letter. GlyphButton = 24 + // SwatchSide is the side of the square of colour on the palette page. Off + // the ladder of controls on purpose: this square is not a target and not a + // mark beside a word, it is the thing being looked at, and at the size of + // a switch's tick two neighbouring surfaces six L* apart read as one. + SwatchSide = space6 // markSide is the side of the square of a switch: the toolkit's inline // icon, the same 20 the glyph above is built round, so a switch and the // button that explains it are one size and stand in one 24 px box.