fix(selection): show the rec mode badge in the neutral tag style
- commit
- 65e5beb177aa2968d938e4f2be2c264414c16ce6
- author
- brice · · 2026-09-30 16:06 UTC
- parent
- 1805145
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +10 -20
M README.md +1 -1
| @@ -15,7 +15,7 @@ make bin | |||
| 15 | 15 | ./bin/nefercap status | |
| 16 | 16 | ``` | |
| 17 | 17 | ||
| 18 | - | `nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A badge at the top left of every overlay always shows the mode: `SHOT`, or a red `● REC`. | |
| 18 | + | `nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A neutral badge at the top left of every overlay always shows the mode: `SHOT` or `REC`. | |
| 19 | 19 | ||
| 20 | 20 | The selector covers the connected outputs, up to nine: | |
| 21 | 21 | ||
| @@ -15,7 +15,7 @@ make bin | |||||
| 15 | ./bin/nefercap status | 15 | ./bin/nefercap status | ||
| 16 | ``` | 16 | ``` | ||
| 17 | 17 | ||||
| 18 | - | `nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A badge at the top left of every overlay always shows the mode: `SHOT`, or a red `● REC`. | 18 | + | `nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A neutral badge at the top left of every overlay always shows the mode: `SHOT` or `REC`. |
| 19 | 19 | ||||
| 20 | The selector covers the connected outputs, up to nine: | 20 | The selector covers the connected outputs, up to nine: | ||
| 21 | 21 | ||||
M internal/adapters/selection/mode_test.go +3 -3
| @@ -29,7 +29,7 @@ func TestTabTogglesModeKeepingTarget(t *testing.T) { | |||
| 29 | 29 | assert.Equal(t, ports.Record, m.mode) | |
| 30 | 30 | assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept") | |
| 31 | 31 | assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText) | |
| 32 | - | assert.Equal(t, "● REC", badgeText(m.mode)) | |
| 32 | + | assert.Equal(t, "REC", badgeText(m.mode)) | |
| 33 | 33 | m.input(key("Tab")) | |
| 34 | 34 | assert.Equal(t, ports.Screenshot, m.mode) | |
| 35 | 35 | assert.Contains(t, m.footerText, "Enter capture monitor") | |
| @@ -70,8 +70,8 @@ func TestTabSharedAcrossOverlaysAndResult(t *testing.T) { | |||
| 70 | 70 | ||
| 71 | 71 | func TestBadgeClassAndText(t *testing.T) { | |
| 72 | 72 | assert.Equal(t, "SHOT", badgeText(ports.Screenshot)) | |
| 73 | - | assert.Equal(t, "● REC", badgeText(ports.Record)) | |
| 74 | - | assert.Contains(t, styleSheet, "box.rec { background-color: #e5484d;") | |
| 73 | + | assert.Equal(t, "REC", badgeText(ports.Record)) | |
| 74 | + | assert.NotContains(t, styleSheet, "box.rec", "the rec badge keeps the neutral tag style") | |
| 75 | 75 | } | |
| 76 | 76 | ||
| 77 | 77 | func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) { | |
| @@ -29,7 +29,7 @@ func TestTabTogglesModeKeepingTarget(t *testing.T) { | |||||
| 29 | assert.Equal(t, ports.Record, m.mode) | 29 | assert.Equal(t, ports.Record, m.mode) | ||
| 30 | assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept") | 30 | assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept") | ||
| 31 | assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText) | 31 | assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText) | ||
| 32 | - | assert.Equal(t, "● REC", badgeText(m.mode)) | 32 | + | assert.Equal(t, "REC", badgeText(m.mode)) |
| 33 | m.input(key("Tab")) | 33 | m.input(key("Tab")) | ||
| 34 | assert.Equal(t, ports.Screenshot, m.mode) | 34 | assert.Equal(t, ports.Screenshot, m.mode) | ||
| 35 | assert.Contains(t, m.footerText, "Enter capture monitor") | 35 | assert.Contains(t, m.footerText, "Enter capture monitor") | ||
| @@ -70,8 +70,8 @@ func TestTabSharedAcrossOverlaysAndResult(t *testing.T) { | |||||
| 70 | 70 | ||||
| 71 | func TestBadgeClassAndText(t *testing.T) { | 71 | func TestBadgeClassAndText(t *testing.T) { | ||
| 72 | assert.Equal(t, "SHOT", badgeText(ports.Screenshot)) | 72 | assert.Equal(t, "SHOT", badgeText(ports.Screenshot)) | ||
| 73 | - | assert.Equal(t, "● REC", badgeText(ports.Record)) | 73 | + | assert.Equal(t, "REC", badgeText(ports.Record)) |
| 74 | - | assert.Contains(t, styleSheet, "box.rec { background-color: #e5484d;") | 74 | + | assert.NotContains(t, styleSheet, "box.rec", "the rec badge keeps the neutral tag style") |
| 75 | } | 75 | } | ||
| 76 | 76 | ||||
| 77 | func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) { | 77 | func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) { | ||
M internal/adapters/selection/scene.go +3 -2
| @@ -61,10 +61,11 @@ func footerText(mode ports.Mode, kind core.PickKind, workspace, toggle bool) str | |||
| 61 | 61 | return b.String() | |
| 62 | 62 | } | |
| 63 | 63 | ||
| 64 | - | // badgeText is the always-visible mode badge; recBadge is styled red. | |
| 64 | + | // badgeText is the always-visible mode badge. Both modes share the neutral | |
| 65 | + | // tag style: a red badge would read as an active recording. | |
| 65 | 66 | func badgeText(mode ports.Mode) string { | |
| 66 | 67 | if mode == ports.Record { | |
| 67 | - | return "● REC" | |
| 68 | + | return "REC" | |
| 68 | 69 | } | |
| 69 | 70 | return "SHOT" | |
| 70 | 71 | } | |
| @@ -61,10 +61,11 @@ func footerText(mode ports.Mode, kind core.PickKind, workspace, toggle bool) str | |||||
| 61 | return b.String() | 61 | return b.String() | ||
| 62 | } | 62 | } | ||
| 63 | 63 | ||||
| 64 | - | // badgeText is the always-visible mode badge; recBadge is styled red. | 64 | + | // badgeText is the always-visible mode badge. Both modes share the neutral |
| 65 | + | // tag style: a red badge would read as an active recording. | |||
| 65 | func badgeText(mode ports.Mode) string { | 66 | func badgeText(mode ports.Mode) string { | ||
| 66 | if mode == ports.Record { | 67 | if mode == ports.Record { | ||
| 67 | - | return "● REC" | 68 | + | return "REC" |
| 68 | } | 69 | } | ||
| 69 | return "SHOT" | 70 | return "SHOT" | ||
| 70 | } | 71 | } | ||
M internal/adapters/selection/style.css +0 -2
| @@ -13,5 +13,3 @@ box.tag { | |||
| 13 | 13 | background-color: rgba(13, 16, 20, 0.92); color: #e6edf3; | |
| 14 | 14 | font-family: monospace; font-size: 1rem; text-align: center; | |
| 15 | 15 | } | |
| 16 | - | /* Recording mode badge: the HUD's red. */ | |
| 17 | - | box.rec { background-color: #e5484d; color: #ffffff; } | |
| @@ -13,5 +13,3 @@ box.tag { | |||||
| 13 | background-color: rgba(13, 16, 20, 0.92); color: #e6edf3; | 13 | background-color: rgba(13, 16, 20, 0.92); color: #e6edf3; | ||
| 14 | font-family: monospace; font-size: 1rem; text-align: center; | 14 | font-family: monospace; font-size: 1rem; text-align: center; | ||
| 15 | } | 15 | } | ||
| 16 | - | /* Recording mode badge: the HUD's red. */ | |||
| 17 | - | box.rec { background-color: #e5484d; color: #ffffff; } | |||
M internal/adapters/selection/view.go +3 -12
| @@ -1,10 +1,6 @@ | |||
| 1 | 1 | package selection | |
| 2 | 2 | ||
| 3 | - | import ( | |
| 4 | - | "github.com/bnema/nefergui" | |
| 5 | - | ||
| 6 | - | "github.com/bnema/nefercap/internal/ports" | |
| 7 | - | ) | |
| 3 | + | import "github.com/bnema/nefergui" | |
| 8 | 4 | ||
| 9 | 5 | // view draws the scene. Geometry goes through Node.Rect, so no CSS strings | |
| 10 | 6 | // are built per frame; classes come from the embedded stylesheet. Boxes with no | |
| @@ -28,13 +24,8 @@ func view(f *nefergui.Frame, m *model) { | |||
| 28 | 24 | if n := tag(st, "label", s.label); n.ok { | |
| 29 | 25 | n.node.Text(m.label.text) | |
| 30 | 26 | } | |
| 31 | - | if s.badge.w > 0 && s.badge.h > 0 { | |
| 32 | - | class := "tag" | |
| 33 | - | if m.mode == ports.Record { | |
| 34 | - | class = "rec" | |
| 35 | - | } | |
| 36 | - | st.Box(nefergui.Key("badge"), nefergui.Class("tag"), nefergui.Class(class)). | |
| 37 | - | Rect(s.badge.x, s.badge.y, s.badge.w, s.badge.h).Text(badgeText(m.mode)) | |
| 27 | + | if n := tag(st, "badge", s.badge); n.ok { | |
| 28 | + | n.node.Text(badgeText(m.mode)) | |
| 38 | 29 | } | |
| 39 | 30 | if n := tag(st, "header", s.header); n.ok { | |
| 40 | 31 | n.node.Text(m.header) | |
| @@ -1,10 +1,6 @@ | |||||
| 1 | package selection | 1 | package selection | ||
| 2 | 2 | ||||
| 3 | - | import ( | 3 | + | import "github.com/bnema/nefergui" |
| 4 | - | "github.com/bnema/nefergui" | |||
| 5 | - | ||||
| 6 | - | "github.com/bnema/nefercap/internal/ports" | |||
| 7 | - | ) | |||
| 8 | 4 | ||||
| 9 | // view draws the scene. Geometry goes through Node.Rect, so no CSS strings | 5 | // view draws the scene. Geometry goes through Node.Rect, so no CSS strings | ||
| 10 | // are built per frame; classes come from the embedded stylesheet. Boxes with no | 6 | // are built per frame; classes come from the embedded stylesheet. Boxes with no | ||
| @@ -28,13 +24,8 @@ func view(f *nefergui.Frame, m *model) { | |||||
| 28 | if n := tag(st, "label", s.label); n.ok { | 24 | if n := tag(st, "label", s.label); n.ok { | ||
| 29 | n.node.Text(m.label.text) | 25 | n.node.Text(m.label.text) | ||
| 30 | } | 26 | } | ||
| 31 | - | if s.badge.w > 0 && s.badge.h > 0 { | 27 | + | if n := tag(st, "badge", s.badge); n.ok { |
| 32 | - | class := "tag" | 28 | + | n.node.Text(badgeText(m.mode)) |
| 33 | - | if m.mode == ports.Record { | |||
| 34 | - | class = "rec" | |||
| 35 | - | } | |||
| 36 | - | st.Box(nefergui.Key("badge"), nefergui.Class("tag"), nefergui.Class(class)). | |||
| 37 | - | Rect(s.badge.x, s.badge.y, s.badge.w, s.badge.h).Text(badgeText(m.mode)) | |||
| 38 | } | 29 | } | ||
| 39 | if n := tag(st, "header", s.header); n.ok { | 30 | if n := tag(st, "header", s.header); n.ok { | ||
| 40 | n.node.Text(m.header) | 31 | n.node.Text(m.header) | ||