Scale two bullet columns independently

A two-bullet-column slide sized both columns with the smaller of the two fit
scales, so a sparse column (e.g. 8 items) was shrunk to a crowded one's size
(e.g. 19 items), leaving the text tiny with empty space below it.

Each column now scales to fill its own height; the column headings keep a
shared size so the layout stays tidy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Brenno de Winter 2026-06-08 22:30:46 +02:00
parent 839474fa19
commit 196cd8adb1
3 changed files with 32 additions and 3 deletions

View file

@ -47,6 +47,8 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).
separate from the slide title. separate from the slide title.
- Slide text auto-sizing now measures with the deck's own font, so text grows to - Slide text auto-sizing now measures with the deck's own font, so text grows to
use the available space more accurately instead of staying smaller than needed. use the available space more accurately instead of staying smaller than needed.
- The two bullet columns now scale **independently**, so a column with few items
is no longer shrunk down to the size of a crowded one beside it.
- Slide transitions in the presenter no longer flash a black frame (neighbour - Slide transitions in the presenter no longer flash a black frame (neighbour
images are precached and `gaplessPlayback` is enabled) — important for images are precached and `gaplessPlayback` is enabled) — important for
recording. recording.

View file

@ -1050,7 +1050,6 @@ class _TwoBulletsPreview extends StatelessWidget {
font: font, font: font,
maxScale: _kBulletsMaxScale, maxScale: _kBulletsMaxScale,
); );
final scale = leftScale < rightScale ? leftScale : rightScale;
return Container( return Container(
color: _hexColor(profile.slideBackgroundColor), color: _hexColor(profile.slideBackgroundColor),
@ -1089,6 +1088,8 @@ class _TwoBulletsPreview extends StatelessWidget {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Each column scales to fill its own height, so a sparse
// column is not shrunk down to a crowded one's size.
_bulletColumn( _bulletColumn(
context, context,
title: col1Title, title: col1Title,
@ -1099,7 +1100,7 @@ class _TwoBulletsPreview extends StatelessWidget {
headingGap: headingGap, headingGap: headingGap,
bulletSize: bulletSize, bulletSize: bulletSize,
bulletGap: bulletGap, bulletGap: bulletGap,
scale: scale, scale: leftScale,
), ),
SizedBox(width: columnGap), SizedBox(width: columnGap),
_bulletColumn( _bulletColumn(
@ -1112,7 +1113,7 @@ class _TwoBulletsPreview extends StatelessWidget {
headingGap: headingGap, headingGap: headingGap,
bulletSize: bulletSize, bulletSize: bulletSize,
bulletGap: bulletGap, bulletGap: bulletGap,
scale: scale, scale: rightScale,
), ),
], ],
), ),

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:ocideck/models/slide.dart'; import 'package:ocideck/models/slide.dart';
import 'package:ocideck/widgets/slides/inline_markdown.dart';
import 'package:ocideck/widgets/slides/slide_preview.dart'; import 'package:ocideck/widgets/slides/slide_preview.dart';
Widget _host(Slide slide) { Widget _host(Slide slide) {
@ -58,6 +59,31 @@ void main() {
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
}); });
testWidgets('two-bullet columns scale independently (sparse stays large)', (
tester,
) async {
final slide = Slide.create(SlideType.twoBullets).copyWith(
bullets: const ['Solo'],
bullets2: List.generate(14, (i) => 'Item $i'),
);
await tester.pumpWidget(_host(slide));
await tester.pump();
double sizeOf(String text) => tester
.widget<InlineMarkdownText>(
find.byWidgetPredicate(
(x) => x is InlineMarkdownText && x.text == text,
),
)
.style
.fontSize!;
// The single-bullet column is not shrunk to the 14-bullet column's size.
expect(sizeOf('Solo'), greaterThan(sizeOf('Item 0') * 1.3));
expect(tester.takeException(), isNull);
});
testWidgets('bullets slide renders an optional subheading below the title', ( testWidgets('bullets slide renders an optional subheading below the title', (
tester, tester,
) async { ) async {