feat/hints a471fa4

style.go RawHistoryFolder

1package hintlabel
2
3import (
4	"image/color"
5	"math"
6	"strconv"
7)
8
9// Style is the resolved, physical-pixel label style and is also a cache key.
10type Style struct {
11	FontPx                 float64
12	Background, Foreground color.RGBA
13}
14
15// FontPixels resolves an explicit logical size or the output's pixel density.
16// Both millimetre dimensions must be plausible for the untransformed mode.
17func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {
18	return min(maxFontPx, fontPixels(width, height, mmWidth, mmHeight, scale, logical))
19}
20
21// maxFontPx bounds the physical font size, whatever the configuration or the
22// scale ask for.
23const maxFontPx = 256
24
25func fontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {
26	if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) {
27		scale = 1
28	}
29	if logical > 0 && !math.IsInf(logical, 0) {
30		return logical * scale
31	}
32	if width > 0 && height > 0 && mmWidth > 0 && mmHeight > 0 {
33		ratio := (float64(width) / float64(height)) / (float64(mmWidth) / float64(mmHeight))
34		if math.Abs(ratio-1) <= .2 {
35			return min(math.Round(40*scale), max(math.Round(13*scale), math.Round(3.2*float64(width)/float64(mmWidth))))
36		}
37	}
38	return 13 * scale * math.Sqrt(max(1, float64(height)/(1080*scale)))
39}
40
41func parseColor(s string, fallback color.RGBA) color.RGBA {
42	if len(s) != 7 || s[0] != '#' {
43		return fallback
44	}
45	n, err := strconv.ParseUint(s[1:], 16, 24)
46	if err != nil {
47		return fallback
48	}
49	return color.RGBA{uint8(n >> 16), uint8(n >> 8), uint8(n), 255}
50}
51
52func luminance(c color.RGBA) float64 {
53	linear := func(v uint8) float64 {
54		x := float64(v) / 255
55		if x <= .04045 {
56			return x / 12.92
57		}
58		return math.Pow((x+.055)/1.055, 2.4)
59	}
60	return .2126*linear(c.R) + .7152*linear(c.G) + .0722*linear(c.B)
61}
62
63func contrast(a, b color.RGBA) float64 {
64	x, y := luminance(a), luminance(b)
65	return (max(x, y) + .05) / (min(x, y) + .05)
66}
67
68// Colors derives ink from the background. Near-black and near-white have a
69// narrow luminance gap where neither meets AA; in that gap move HSL
70// lightness away from .5, preserving hue and saturation.
71// Explicit foreground overrides are honoured as supplied.
72func Colors(active, background, foreground string) (color.RGBA, color.RGBA) {
73	bg := parseColor(background, parseColor(active, color.RGBA{128, 128, 128, 255}))
74	dark, light := color.RGBA{17, 17, 17, 255}, color.RGBA{245, 245, 245, 255}
75	ink := dark
76	if contrast(bg, light) > contrast(bg, dark) {
77		ink = light
78	}
79	for contrast(bg, ink) < 4.5 {
80		// Move HSL lightness toward zero while preserving hue and saturation.
81		hi, lo := max(bg.R, bg.G, bg.B), min(bg.R, bg.G, bg.B)
82		l := (float64(hi) + float64(lo)) / 510
83		if l > .5 {
84			// Above .5, moving toward white increases contrast with dark ink.
85			bg.R += uint8(math.Ceil(float64(255-bg.R) * .01))
86			bg.G += uint8(math.Ceil(float64(255-bg.G) * .01))
87			bg.B += uint8(math.Ceil(float64(255-bg.B) * .01))
88			ink = dark
89		} else {
90			bg.R = uint8(float64(bg.R) * .99)
91			bg.G = uint8(float64(bg.G) * .99)
92			bg.B = uint8(float64(bg.B) * .99)
93			ink = light
94		}
95	}
96	return bg, parseColor(foreground, ink)
97}
98
99func mix(ink, bg color.RGBA) color.RGBA {
100	channel := func(a, b uint8) uint8 { return uint8(math.Round(.45*float64(a) + .55*float64(b))) }
101	return color.RGBA{channel(ink.R, bg.R), channel(ink.G, bg.G), channel(ink.B, bg.B), 255}
102}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close