diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 09:12:32 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 09:12:32 +0200 |
| commit | 4d6386960c981678a8e8123a4db5463df7ee60bb (patch) | |
| tree | 403877da13c244b68ef335f25ba1337cd72a6527 /gui/internal/ui | |
| parent | 80dde7b2cae7b9844c244e03395145370a819412 (diff) | |
| download | krino-4d6386960c981678a8e8123a4db5463df7ee60bb.tar.gz krino-4d6386960c981678a8e8123a4db5463df7ee60bb.zip | |
gui: keep every divider where it is put, and let the columns shrink
Diffstat (limited to 'gui/internal/ui')
| -rw-r--r-- | gui/internal/ui/plan.go | 92 | ||||
| -rw-r--r-- | gui/internal/ui/window.go | 29 |
2 files changed, 97 insertions, 24 deletions
diff --git a/gui/internal/ui/plan.go b/gui/internal/ui/plan.go index 38412c2..4fa9c39 100644 --- a/gui/internal/ui/plan.go +++ b/gui/internal/ui/plan.go @@ -36,12 +36,13 @@ type planView struct { selNone *gtk.Button checked *gtk.MenuButton - groups [6]*gtk.SizeGroup - filter *gtk.SearchEntry - shown []int - list *gtk.ListBox - headerBox *gtk.Box - details *gtk.TextView + groups [6]*gtk.SizeGroup + listScroll *gtk.ScrolledWindow + filter *gtk.SearchEntry + shown []int + list *gtk.ListBox + headerBox *gtk.Box + details *gtk.TextView previewNote *gtk.Label picture *gtk.Picture @@ -124,6 +125,7 @@ func newPlanView(w *Window) *planView { listScroll.SetChild(p.list) listScroll.SetHExpand(true) listScroll.SetVExpand(true) + p.listScroll = listScroll for i := range p.groups { p.groups[i] = gtk.NewSizeGroup(gtk.SizeGroupHorizontal) } @@ -567,6 +569,12 @@ func (p *planView) fillList() { p.selAll.SetSensitive(!p.tab.Applied) p.selNone.SetSensitive(!p.tab.Applied) p.sayWhatIsShown() + // A fresh list starts at its left edge: without this the view can open + // scrolled sideways, with the file names out of sight (his report, + // 2026-09-17). + if adj := p.listScroll.HAdjustment(); adj != nil { + adj.SetValue(0) + } } // widths is how wide each column has to be for this plan: enough for the @@ -602,6 +610,10 @@ func (p *planView) showOutcome() bool { return p.tab != nil && p.tab.Applied } +// headerFloors are how narrow each heading may become, matching the cells +// under it. +var headerFloors = [5]int{12, 6, 16, 10, 8} + // columnTitles name the columns of the plan. var columnTitles = [5]string{"file", "action", "where it would go", "rule", "outcome"} @@ -625,7 +637,7 @@ func (p *planView) header(w [5]int) { if i == 4 && !p.showOutcome() { continue } - l := columnMin(title, w[i], yieldChars, i == 0 || i == 2) + l := columnMin(title, w[i], headerFloors[i], i == 0 || i == 2) l.AddCSSClass("heading") l.SetTooltipText("") p.groups[i+1].AddWidget(l) @@ -658,16 +670,19 @@ func (p *planView) rowWidget(i int, r model.Row, w [5]int) *gtk.ListBoxRow { // shrink first; the action and the rule keep their width, so neither is // ever the column cut to an ellipsis. action, colour := rowAction(r) + // Every column can shrink: a pane narrower than their natural widths + // used to push the whole row out of view to the left (his report, + // 2026-09-17). cells := []gtk.Widgetter{ - column(escape(r.Rel), w[0], true), + columnMin(escape(r.Rel), w[0], 12, true), colouredColumn(action, w[1], colour), - columnMin(escape(rowWhere(r, p.dirRoot())), w[2], 18, true), - column(escape(r.Rule), w[3], false), + columnMin(escape(rowWhere(r, p.dirRoot())), w[2], 16, true), + columnMin(escape(r.Rule), w[3], 10, false), } // The outcome column appears once a plan has been applied: before that // it would be an empty column with a heading over it. if p.showOutcome() { - cells = append(cells, column(escape(r.Outcome), w[4], false)) + cells = append(cells, columnMin(escape(r.Outcome), w[4], 8, false)) } for i, cell := range cells { p.groups[i+1].AddWidget(cell) @@ -864,17 +879,52 @@ func (p *planView) setLayout(which string) { p.arrangement.SetResizeEndChild(false) p.arrangement.SetShrinkEndChild(false) p.arrangement.SetVExpand(true) + prefs := p.w.prefs if which == model.LayoutSide { p.detailPane.SetSizeRequest(340, -1) - p.arrangement.SetPosition(780) + p.arrangement.SetPosition(orDefault(prefs.ListWidth, model.DefaultListWidth)) + p.previewHeight = orDefault(prefs.PreviewHeight, model.DefaultPreviewHeight) } else { p.detailPane.SetSizeRequest(-1, 240) - p.arrangement.SetPosition(420) + p.arrangement.SetPosition(orDefault(prefs.ListHeight, model.DefaultListHeight)) + p.previewHeight = orDefault(prefs.PreviewWidth, model.DefaultPreviewWidth) } + p.arrangement.Connect("notify::position", p.rememberListSize) p.root.Append(p.arrangement) p.setPreviewHeight(p.previewHeight) } +// orDefault is n, or the default when nothing has been chosen yet. +func orDefault(n, def int) int { + if n < 80 { + return def + } + return n +} + +// rememberListSize keeps where the divider between the file list and the +// explanation was left, for this layout. +func (p *planView) rememberListSize() { + at := p.arrangement.Position() + if at < 80 { + return + } + prefs := p.w.prefs + if p.layout == model.LayoutStacked { + if prefs.ListHeight == at { + return + } + prefs.ListHeight = at + } else { + if prefs.ListWidth == at { + return + } + prefs.ListWidth = at + } + p.w.prefs = prefs + p.w.savePrefsSoon() +} + // rememberPreviewHeight keeps what a drag of the divider chose, so the next // window opens the same way. func (p *planView) rememberPreviewHeight() { @@ -888,11 +938,13 @@ func (p *planView) rememberPreviewHeight() { } p.previewHeight = height prefs := p.w.prefs - prefs.PreviewHeight = height - p.w.prefs = prefs - if err := prefs.Save(); err != nil { - p.w.setStatus("settings: %v", err) + if p.layout == model.LayoutStacked { + prefs.PreviewWidth = height + } else { + prefs.PreviewHeight = height } + p.w.prefs = prefs + p.w.savePrefsSoon() } // setPreviewHeight puts the divider where a height asks for. @@ -926,7 +978,11 @@ func (p *planView) applyPrefs(prefs model.Prefs) { p.previewOff = !prefs.Preview p.startSelected = prefs.SelectAll p.setLayout(prefs.Layout) - p.setPreviewHeight(prefs.PreviewHeight) + if prefs.Layout == model.LayoutStacked { + p.setPreviewHeight(orDefault(prefs.PreviewWidth, model.DefaultPreviewWidth)) + } else { + p.setPreviewHeight(orDefault(prefs.PreviewHeight, model.DefaultPreviewHeight)) + } if p.previewOff { p.previewFor = "" p.picture.SetVisible(false) diff --git a/gui/internal/ui/window.go b/gui/internal/ui/window.go index 61fe157..06bce05 100644 --- a/gui/internal/ui/window.go +++ b/gui/internal/ui/window.go @@ -11,6 +11,7 @@ import ( "os" "strings" + "github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/gtk/v4" "krino/gui/internal/model" @@ -26,12 +27,13 @@ type Window struct { engine *engine.Engine - plan *planView - history *historyView - rules *rulesView - status *gtk.Label - prefs model.Prefs - leaving bool + plan *planView + history *historyView + rules *rulesView + status *gtk.Label + prefs model.Prefs + leaving bool + saveTimer glib.SourceHandle } // NewWindow builds the window for e. Each plan and each undo is its own @@ -147,6 +149,21 @@ func (w *Window) confirmLeaving() { d.Show() } +// savePrefsSoon writes the preferences a moment after the last change, so +// dragging a divider does not write the file on every pixel. +func (w *Window) savePrefsSoon() { + if w.saveTimer != 0 { + glib.SourceRemove(w.saveTimer) + } + w.saveTimer = glib.TimeoutAdd(500, func() bool { + w.saveTimer = 0 + if err := w.prefs.Save(); err != nil { + w.setStatus("settings: %v", err) + } + return false + }) +} + // applyPrefs takes a change from the settings window: the font of the // editor, whether the configuration is coloured, and whether the file // behind a row is shown. What is already on screen changes at once. |
