Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,110 @@ because it turns other people's test suites red.

## [Unreleased]

### Changed

- **The window draws its own buttons, switches and choosers.** A button now
has one filled face for the action that does the work and an outline for the
ones beside it, lightens under the pointer, darkens when pressed, and shows a
clear ring in the accent colour when the keyboard is on it - where the
toolkit's own button blended that ring into the fill so it could not be seen
on the filled one, and had no pressed look at all. Enter presses a button as
well as the space bar. The three ways of stating a size are one segmented
switch instead of three circles: one keyboard stop the arrow keys step
through. A tick-box switch stands under its name in the column like every
other field rather than carrying its own words, and a caption under a field
now recedes to the same quiet grey as a hint rather than sitting a shade
brighter.

- **The window lays every form out as a grid.** A field is one row now: its
name in a column of names, its box beside it, and every box on a screen
starting on the same edge. Until now the name stood over the box, so each
field cost two rows, and the small button that opens a field's explanation
was the tallest thing on the name's line - the name of a box floated 13 px
above the box's middle. Names are regular weight, the one bold thing on a
screen is the title of a section, and the count of bytes a size comes to
stands right after the size box instead of at the far end of the line.

Underneath it, every distance in the window is one of six steps and every
number about the look lives in one file, so the gaps between things are the
ones somebody chose. The settings a format declares stand one to a row -
two of them no longer share a line - and the three ways of stating a size
on the batch screen keep their switch above one box, in the column of
controls.

- **The line under the buttons says what the run comes to, before anything
is pressed.** How many files, how many bytes, what kinds and where, on one
line, worked out from the form as it is typed - with no disk read and no
planning. A range says "between" its two ends until Preview draws the
sizes, a container sized by its contents says so, and a form that cannot
be added up yet names only the destination. After Preview the line is exact
and the destination carries the room left on its disk. A change of the
form puts the summary back over whatever a press said, where until now the
line named the destination until the first press and never again.

The refusal about a run as a whole now scrolls inside the same room as the
run's other messages, so the bar no longer grows when a run is refused, and
choosing from a menu reaches the live check the way typing in a box does.

- **The tabs across the top stand on the same edge as the screen under them,
and a screen has one name.** The strip is drawn by the tool now rather than
by the toolkit: its words start where the title and every field name start,
the chosen one carries a 2 px mark in the accent colour, and a word lights up
under the pointer. The title of a work screen is the word on its tab -
Single batch, Presets, Several batches - and under it a quiet sentence says
what the screen is for, where until now each screen had one name on the tab
and another over the form.

- **An open list is as tall as half the window, not eight rows.** The list a
menu drops down stopped at eight rows in every window - 224 px at 800x600
and at 1100x1300 alike - so a third of the twenty-four formats showed
however tall the window was. It now covers up to half the window's height,
in whole rows: ten rows in a window 600 px tall, eighteen in one 1025 px
tall, and the rest under a scroll. It still opens upward when there is more
room above the box than below it, and is still cut to the room on whichever
side it lands.

- **The window is set in Inter.** The window was drawn in Noto Sans, the
toolkit's own face. It is set in Inter 4.1 now, Regular and Bold, embedded
in the window binary under the Open Font License - the licence notice and
`tfg-gui`'s About screen name it beside the other bundled work. The window
binary is about 800 kB larger for it. The command line binary carries no
font and is unchanged.

### Added

- **Every control shows where the keyboard is and answers the pointer.** Every
place the keyboard can land - a box, a menu, a switch, a button, the
segmented switch, a word on the tab strip - draws the same 2 px ring when a
key put it there and not when a press did, and every control you can click
lights up under the pointer. Buttons gain a pressed look. This closes the
gap where some stops drew a focus mark too faint to see and one drew none at
all.

- **The tabs can be worked from the keyboard.** Tab reaches each word on the
strip, Enter or Space opens its screen and puts the keyboard on the first
field with its mark showing, and the arrow keys move along the strip without
opening anything. The toolkit's tabs answered the mouse only.

- **`tfg-gui --catalogue` opens a catalogue of every part of the window in
every state it has.** A hidden screen for anybody changing the window's
look: each button, box, menu, switch, list, tab and rank of text, at rest,
under the pointer, holding the keyboard, refused, disabled and given a
sentence too long for it - sixteen entries and ninety-three states, drawn
at the widths a form gives them. `--catalog` is accepted too. Any other
argument is ignored, as every argument was until now.

### Fixed

- **A refused preset no longer carries the note of the preset before it.** A
limit of 512 B was refused with "no limit was given" under it - a note left
over from the last expansion that worked.

- **A preview asks the disk how much room it has from the worker, not from
the interface thread.** The rest of a preview moved off that thread on
2026-08-26 and this one read stayed behind, so a directory on a slow share
could still stop the window from drawing for as long as the share took.

- **Asking for damaged files and declaring they will be accepted is now refused
on the command line too.** A damaged file is one a reader was measured to
refuse, so `--expected accept` beside `--damage` asks for something nothing
Expand All @@ -41,6 +143,11 @@ because it turns other people's test suites red.
and the site has a section explaining how to produce a file that is broken on
purpose.

- **The window spells a byte count the way the command line does.** The count
under a size box said `10485760 B` while `tfg` said `10 485 760 B` about the
same number - the window had a spelling of its own that the grouping of
digits in 0.3.0 never reached. There is one spelling now.

## [0.3.0] - 2026-09-09

### Breaking
Expand Down
18 changes: 12 additions & 6 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,11 @@ THE SOFTWARE.
The toolkit compiles fonts and drawings into the binary. They are not modules,
so a list of modules never mentions them - which is how they shipped unnamed
until this section was written on 2026-08-28. Seven fonts and ninety-seven
images arrive this way, and their licences ask for their notices to travel with
them.
images arrive this way from the toolkit, and their licences ask for their
notices to travel with them. Two more font files arrive from a package of this
project itself: Inter, the face the window has been set in since 2026-09-15,
is embedded by `internal/gui/font` rather than by the toolkit, and is listed
here under the same rule.

What is listed here was measured by asking the compiler which files each
package embeds, not by reading its source. The copyright lines were read out of
Expand All @@ -603,7 +606,8 @@ the font files themselves.
| what | comes from | licence | copyright |
|---|---|---|---|
| Noto Sans, four styles | `fyne.io/fyne/v2/theme` | OFL-1.1 | Copyright 2015 Google Inc. All Rights Reserved. |
| Inter, symbols only | `fyne.io/fyne/v2/theme` | OFL-1.1 | (c) 2020 The Inter Project Authors |
| Inter Symbols, the toolkit's symbol glyphs only | `fyne.io/fyne/v2/theme` | OFL-1.1 | (c) 2020 The Inter Project Authors |
| Inter, Regular and Bold, the text of the window - static instances from the v4.1 release of github.com/rsms/inter | `internal/gui/font` in this project | OFL-1.1 | Copyright 2016 The Inter Project Authors |
| DejaVu Sans Mono for Powerline | `fyne.io/fyne/v2/theme` | Bitstream-Vera | (c) 2003 Bitstream, Inc. DejaVu changes are in the public domain |
| EmojiOne Color | `fyne.io/fyne/v2/theme` | MIT, and read the note below | Copyright 2016 Adobe Systems Incorporated |
| Fyne icon set, 96 drawings and one image | `fyne.io/fyne/v2/theme` | BSD-3-Clause | (C) 2018 Fyne.io developers (see AUTHORS) |
Expand All @@ -612,8 +616,10 @@ the font files themselves.

`tfg`, the command line binary, embeds none of this. It has no toolkit in it.

Neither font licence declares a Reserved Font Name, so the identifier above is
OFL-1.1 rather than OFL-1.1-RFN. Inter is a trademark of Rasmus Andersson.
None of the three fonts under the Open Font License declares a Reserved Font
Name - checked in the licence file of each and in the name table of each font
file - so the identifier above is OFL-1.1 rather than OFL-1.1-RFN. Inter is a
trademark of Rasmus Andersson.

**EmojiOne Color says two different things about itself.** The toolkit ships it
with an MIT licence naming Adobe Systems Incorporated. The font's own metadata
Expand All @@ -633,7 +639,7 @@ and only those were replaced by their plain equivalents.

### SIL Open Font License 1.1

Applies to Noto Sans and to Inter.
Applies to Noto Sans, to Inter Symbols and to Inter.

```
------------------------------
Expand Down
2 changes: 1 addition & 1 deletion cmd/tfg-gui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ import (
)

func main() {
os.Exit(gui.Run(os.Stderr))
os.Exit(gui.Run(os.Args[1:], os.Stderr))
}
4 changes: 2 additions & 2 deletions internal/guard/actionbar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"testing"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget"

"github.com/donislawdev/TestingFilesGenerator/internal/gui/parts"
"github.com/donislawdev/TestingFilesGenerator/internal/gui/text"
)

Expand Down Expand Up @@ -68,7 +68,7 @@ func TestTheButtonsThatRunSomethingSitInTheMiddle(t *testing.T) {
}

// rowHolding is the container one control is directly inside.
func rowHolding(o fyne.CanvasObject, want *widget.Button) *fyne.Container {
func rowHolding(o fyne.CanvasObject, want *parts.Button) *fyne.Container {
var found *fyne.Container
walk(o, func(obj fyne.CanvasObject) {
box, ok := obj.(*fyne.Container)
Expand Down
90 changes: 61 additions & 29 deletions internal/guard/actionbarheight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,17 @@ func TestTheFormDoesNotMoveWhenARunStarts(t *testing.T) {
restingStates := []struct {
name string
clearTheBox bool
wantResting bool
whyItIsWorthA string
}{
{
name: "with a destination on the line",
whyItIsWorthA: "the ordinary path, where the status line already carries the output folder",
},
{
name: "with nothing to say",
name: "with the destination cleared",
clearTheBox: true,
wantResting: true,
whyItIsWorthA: "the state the reserve exists for - no destination, so the line is hidden " +
"and costs nothing until a run speaks",
whyItIsWorthA: "the line still says what the form comes to, without a destination - " +
"a different length of line in the same reserve",
},
}

Expand All @@ -94,14 +92,17 @@ func TestTheFormDoesNotMoveWhenARunStarts(t *testing.T) {
settle(content, w)
}

// Whether the line is actually hidden is asserted rather than
// assumed. If a later change keeps something on it at rest,
// this state stops being the state the reserve is for, and
// this guard has to say so instead of quietly measuring the
// other one twice - which is exactly how it went blind before.
if state.wantResting && status.Visible() {
t.Fatalf("the status line still says %q with no destination, so this guard is not in "+
"the state it means to check (%s)", status.Text, state.whyItIsWorthA)
// Whether the line is in the state this case names is asserted
// rather than assumed. If a later change keeps naming a
// destination that was cleared, this guard has to say so
// instead of quietly measuring the other case twice - which is
// exactly how it went blind before. A hidden line is a legal
// state here rather than a wrong one: a form that does not
// settle and has no destination has nothing to say, which is
// the batch screen as it opens.
if state.clearTheBox && status.Visible() && strings.Contains(status.Text, text.WillGoTo("")) {
t.Fatalf("the status line still names a destination after the box was cleared: %q (%s)",
status.Text, state.whyItIsWorthA)
}

atRest := scroll.Size().Height
Expand Down Expand Up @@ -185,13 +186,11 @@ func TestWhatARunSaysComesBeforeWhatSettlingSaid(t *testing.T) {
t.Fatalf("the preview said %q, which is one line - this guard needs a run that also carries "+
"a note, or it is checking the order of a list with one thing in it", status.Text)
}
// Matched on the tail of the preview's own sentence rather than on a word
// like "file", because the note talks about files too - an earlier version
// of this checked for that and stayed green with the order reversed, which
// is a guard that reads like one and is not.
marker := text.PreviewCost(1, nil, "1 B")
tail := marker[strings.LastIndex(marker, " ")+1:]
if !strings.Contains(lines[0], tail) {
// Matched on the tail the preview puts on its own line rather than on a
// word like "file", because the note talks about files too - an earlier
// version of this checked for that and stayed green with the order
// reversed, which is a guard that reads like one and is not.
if !strings.HasSuffix(lines[0], text.AndNothingWrittenYet()) {
t.Errorf("the first line of the status is %q, and the preview's own sentence is not it.\n"+
"That sentence has to come first, because the room for these messages is a ceiling and "+
"the message scrolls inside it - so the first line is the only one certain to be read. "+
Expand Down Expand Up @@ -305,17 +304,23 @@ func runMessages(o fyne.CanvasObject) (*parts.Progress, *widget.Label) {
var foundBar *parts.Progress
var foundLabel *widget.Label
for _, child := range box.Objects {
// The label has to be a child of this box, because that is what
// says this is the row a run talks in. The track is looked for
// underneath the child instead: it is wrapped in parts.Slim since
// 2026-08-19, and a guard that insisted on a bare widget here read
// the wrapper and declared the screen had no progress bar.
if it, ok := child.(*widget.Label); ok {
foundLabel = it
continue
}
// The track is looked for underneath the child rather than as the
// child itself: it is wrapped in parts.Slim since 2026-08-19, and a
// guard that insisted on a bare widget here read the wrapper and
// declared the screen had no progress bar. The label likewise
// since 2026-09-14, when the line went under the same override
// every other word on the form stands in (parts.Flush).
//
// The first child holding one label and nothing to press or type
// in is the line a run talks on. The pairing has to stay that
// tight, because the box at the root of a screen also holds a
// track somewhere beneath it and a form full of labels beside it.
if it := progressUnder(child); it != nil {
foundBar = it
continue
}
if foundLabel == nil {
foundLabel = soleLabelUnder(child)
}
}
if foundBar != nil && foundLabel != nil {
Expand All @@ -325,6 +330,33 @@ func runMessages(o fyne.CanvasObject) (*parts.Progress, *widget.Label) {
return bar, status
}

// soleLabelUnder is the one label beneath an object that holds nothing else
// a person could act on, or nil when the object is anything more than a line
// of words.
func soleLabelUnder(o fyne.CanvasObject) *widget.Label {
var found *widget.Label
labels, controls := 0, 0
walk(o, func(obj fyne.CanvasObject) {
switch it := obj.(type) {
case *widget.Label:
labels++
found = it
case *parts.Progress:
controls++
default:
// Anything that can be disabled is a control - a button, a box, a
// menu, a switch, and every wrapper of ours round one of those.
if _, can := obj.(fyne.Disableable); can {
controls++
}
}
})
if labels != 1 || controls > 0 {
return nil
}
return found
}

// progressUnder finds the progress track at or beneath an object.
//
// The type changed on 2026-08-20 when the track became a control of ours, and
Expand Down
3 changes: 1 addition & 2 deletions internal/guard/actionbarpadding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/test"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"

"github.com/donislawdev/TestingFilesGenerator/internal/gui/parts"
Expand Down Expand Up @@ -33,7 +32,7 @@ func TestTheActionBarCostsItsContentPlusOneInset(t *testing.T) {
inside := widget.NewLabel("what a run has to say")
bar := parts.ActionBar(nil, inside)

want := inside.MinSize().Height + theme.Padding()*2
want := inside.MinSize().Height + parts.InsetBar*2
got := bar.MinSize().Height

// Half a pixel, because a rounded corner and a stroke are drawn on the
Expand Down
Loading
Loading