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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class FakeSlideCaptureService extends SlideCaptureService {
SlideCaptureQuality quality = SlideCaptureQuality.thumbnail,
required SlideConfiguration slide,
required BuildContext context,
bool includeDebugLayout = false,
}) async {
return bytes;
}
Expand Down
7 changes: 5 additions & 2 deletions packages/superdeck/lib/src/capture/slide_capture_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ class SlideCaptureService {
SlideCaptureQuality quality = SlideCaptureQuality.thumbnail,
required SlideConfiguration slide,
required BuildContext context,
bool includeDebugLayout = false,
}) async {
final queueKey = shortHash(slide.key + quality.name);
final queueKey = shortHash(
'${slide.key}${quality.name}$includeDebugLayout',
);
try {
while (_generationQueue.length >= _maxConcurrentGenerations) {
await Future.delayed(_kQueuePollInterval);
Expand All @@ -55,7 +58,7 @@ class SlideCaptureService {
_generationQueue.add(queueKey);

final staticRenderingSlide = slide.copyWith(
debug: false,
debug: includeDebugLayout,
isStaticRendering: true,
);

Expand Down
40 changes: 19 additions & 21 deletions packages/superdeck/lib/src/rendering/blocks/block_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:math' as math;

import 'package:flutter/material.dart' show Colors;
import 'package:flutter/widgets.dart';
import 'package:mix/mix.dart';
import 'package:superdeck_core/superdeck_core.dart';
Expand All @@ -12,6 +11,7 @@ import '../../ui/widgets/error_widgets.dart';
import '../../ui/widgets/overflow_clip.dart';
import '../../ui/widgets/provider.dart';
import '../../utils/converters.dart';
import '../layout_debug_overlay.dart';
import 'block_provider.dart';
import 'markdown_viewer.dart';

Expand Down Expand Up @@ -141,13 +141,11 @@ class _BlockContainerState extends State<_BlockContainer> {
),
);

// Add debug border if needed
if (widget.configuration.debug) {
content = DecoratedBox(
position: DecorationPosition.foreground,
decoration: BoxDecoration(
border: Border.all(color: Colors.cyan, width: 2),
),
final debugLayoutEnabled = widget.configuration.debug;
if (debugLayoutEnabled) {
content = BlockLayoutDebugOverlay(
margin: spec.blockContainer.spec.margin,
padding: spec.blockContainer.spec.padding,
child: content,
);
}
Expand Down Expand Up @@ -336,20 +334,15 @@ class SectionWidget extends StatelessWidget {
final SectionBlock section;
final int sectionIndex;

Positioned _renderDebugInfo(Block block, Size size) {
const textStyle = TextStyle(color: Colors.black, fontSize: 12);
final label =
'''
@${block.type}
${size.width.toStringAsFixed(2)} x ${size.height.toStringAsFixed(2)}''';

Positioned _renderDebugInfo(Block block, int blockIndex, Size size) {
return Positioned(
top: 0,
right: 0,
child: Container(
color: Colors.cyan,
padding: const EdgeInsets.all(8),
child: Text(label, style: textStyle),
child: LayoutDebugLabel(
color: debugBlockColor,
text:
'BLOCK ${blockIndex + 1} @${block.type}\n'
'${size.width.toStringAsFixed(0)} × ${size.height.toStringAsFixed(0)}',
),
);
}
Expand Down Expand Up @@ -401,13 +394,18 @@ ${size.width.toStringAsFixed(2)} x ${size.height.toStringAsFixed(2)}''';
),
};

if (!configuration.debug) return blockWidget;
final debugLayoutEnabled = configuration.debug;
if (!debugLayoutEnabled) return blockWidget;
return LayoutBuilder(
builder: (context, constraints) => Stack(
fit: StackFit.expand,
children: [
blockWidget,
_renderDebugInfo(block, constraints.biggest),
_renderDebugInfo(
block,
blockIndex,
constraints.biggest,
),
],
),
);
Expand Down
193 changes: 193 additions & 0 deletions packages/superdeck/lib/src/rendering/layout_debug_overlay.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import 'dart:math' as math;

import 'package:flutter/widgets.dart';

const debugSectionColor = Color(0xFFFF4DCD);
const debugBlockColor = Color(0xFF00D5FF);
const debugMarginColor = Color(0xFFFFA62B);
const debugPaddingColor = Color(0xFF35E06F);

/// Draws a debug-only frame without participating in layout.
class LayoutDebugFrame extends StatelessWidget {
final Color color;
final Widget child;
final double strokeWidth;

const LayoutDebugFrame({
super.key,
required this.color,
required this.child,
this.strokeWidth = 3,
});

@override
Widget build(BuildContext context) {
return CustomPaint(
foregroundPainter: _FramePainter(color, strokeWidth),
child: child,
);
}
}

/// Draws the allocated block, resolved margin, and resolved padding edges.
class BlockLayoutDebugOverlay extends StatelessWidget {
final EdgeInsetsGeometry? margin;
final EdgeInsetsGeometry? padding;
final Widget child;

const BlockLayoutDebugOverlay({
super.key,
required this.margin,
required this.padding,
required this.child,
});

@override
Widget build(BuildContext context) {
final textDirection = Directionality.maybeOf(context) ?? TextDirection.ltr;
return CustomPaint(
foregroundPainter: _BlockInsetsPainter(
margin: margin?.resolve(textDirection) ?? EdgeInsets.zero,
padding: padding?.resolve(textDirection) ?? EdgeInsets.zero,
),
child: child,
);
}
}

class LayoutDebugLabel extends StatelessWidget {
final Color color;
final String text;

const LayoutDebugLabel({super.key, required this.color, required this.text});

@override
Widget build(BuildContext context) {
return ColoredBox(
color: color,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
child: Text(
text,
style: const TextStyle(
color: Color(0xFF071018),
fontSize: 11,
fontWeight: FontWeight.w700,
height: 1.2,
),
),
),
);
}
}

class LayoutDebugLegend extends StatelessWidget {
const LayoutDebugLegend({super.key});

@override
Widget build(BuildContext context) {
return ColoredBox(
color: const Color(0xE6071018),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
_LegendItem(color: debugSectionColor, label: 'SECTION'),
SizedBox(width: 10),
_LegendItem(color: debugBlockColor, label: 'BLOCK'),
SizedBox(width: 10),
_LegendItem(color: debugMarginColor, label: 'MARGIN'),
SizedBox(width: 10),
_LegendItem(color: debugPaddingColor, label: 'PADDING'),
],
),
),
);
}
}

class _LegendItem extends StatelessWidget {
final Color color;
final String label;

const _LegendItem({required this.color, required this.label});

@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox.square(dimension: 10, child: ColoredBox(color: color)),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
],
);
}
}

class _BlockInsetsPainter extends CustomPainter {
final EdgeInsets margin;
final EdgeInsets padding;

const _BlockInsetsPainter({required this.margin, required this.padding});

@override
void paint(Canvas canvas, Size size) {
final blockRect = Offset.zero & size;
final marginRect = _inset(blockRect, margin);
final paddingRect = _inset(marginRect, padding);

_drawFrame(canvas, blockRect, debugBlockColor, 4);
_drawFrame(canvas, marginRect, debugMarginColor, 3);
_drawFrame(canvas, paddingRect, debugPaddingColor, 2);
}

@override
bool shouldRepaint(_BlockInsetsPainter oldDelegate) {
return oldDelegate.margin != margin || oldDelegate.padding != padding;
}
}

class _FramePainter extends CustomPainter {
final Color color;
final double strokeWidth;

const _FramePainter(this.color, this.strokeWidth);

@override
void paint(Canvas canvas, Size size) {
_drawFrame(canvas, Offset.zero & size, color, strokeWidth);
}

@override
bool shouldRepaint(_FramePainter oldDelegate) {
return oldDelegate.color != color || oldDelegate.strokeWidth != strokeWidth;
}
}

Rect _inset(Rect rect, EdgeInsets insets) {
final left = math.min(rect.right, rect.left + insets.left);
final top = math.min(rect.bottom, rect.top + insets.top);
final right = math.max(left, rect.right - insets.right);
final bottom = math.max(top, rect.bottom - insets.bottom);
return Rect.fromLTRB(left, top, right, bottom);
}

void _drawFrame(Canvas canvas, Rect rect, Color color, double strokeWidth) {
if (rect.width <= strokeWidth || rect.height <= strokeWidth) return;
canvas.drawRect(
rect.deflate(strokeWidth / 2),
Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth,
);
}
47 changes: 34 additions & 13 deletions packages/superdeck/lib/src/rendering/slides/slide_view.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:flutter/material.dart' show Colors;
import 'package:flutter/widgets.dart';
import 'package:mix/mix.dart';
import 'package:superdeck_core/superdeck_core.dart';
Expand All @@ -7,6 +6,7 @@ import '../../deck/slide_configuration.dart';
import '../../styling/components/slide.dart';
import '../../utils/constants.dart';
import '../blocks/block_widget.dart';
import '../layout_debug_overlay.dart';

class SlideView extends StatelessWidget {
final SlideConfiguration slide;
Expand All @@ -18,18 +18,20 @@ class SlideView extends StatelessWidget {
: const SizedBox.shrink();
}

Positioned _renderDebugInfo(SectionBlock section, Size slideSize) {
final label = '''
@section | blocks: ${section.blocks.length} | ${slideSize.width.toStringAsFixed(2)} x ${slideSize.height.toStringAsFixed(2)} | align: ${section.align} | flex: ${section.flex}''';

const textStyle = TextStyle(color: Colors.black, fontSize: 12);
Positioned _renderDebugInfo(
SectionBlock section,
int sectionIndex,
Size slideSize,
) {
return Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.cyan,
padding: const EdgeInsets.all(8),
child: Text(label, style: textStyle),
right: 0,
child: LayoutDebugLabel(
color: debugSectionColor,
text:
'SECTION ${sectionIndex + 1} blocks:${section.blocks.length} '
'${slideSize.width.toStringAsFixed(0)} × '
'${slideSize.height.toStringAsFixed(0)}',
),
);
}
Expand Down Expand Up @@ -57,9 +59,22 @@ class SlideView extends StatelessWidget {
return Stack(
fit: StackFit.expand,
children: [
SectionWidget(section: section, sectionIndex: sectionIndex),
if (configuration.debug)
_renderDebugInfo(section, sectionSize),
LayoutDebugFrame(
color: debugSectionColor,
strokeWidth: 4,
child: SectionWidget(
section: section,
sectionIndex: sectionIndex,
),
)
else
SectionWidget(
section: section,
sectionIndex: sectionIndex,
),
if (configuration.debug)
_renderDebugInfo(section, sectionIndex, sectionSize),
],
);
},
Expand Down Expand Up @@ -133,6 +148,12 @@ class SlideView extends StatelessWidget {
},
),
),
if (slide.debug)
const Positioned(
left: 8,
bottom: 8,
child: IgnorePointer(child: LayoutDebugLegend()),
),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import 'package:mix/mix.dart';
/// It intentionally cannot express widget modifiers, width/height or other
/// constraints, transforms, box alignment, or widget-state variants — those
/// would create competing geometry owners with section `spacing`, block
/// `flex`, and block/section `align`. Extending this allow-list is a design
/// decision, not a convenience patch; see `.planning` notes for PR #99.
/// `flex`, and block/section `align`. Extending this allow-list is a framework
/// design decision because it changes which layer owns block geometry.
final class BlockStyler extends Style<BoxSpec>
with
Diagnosticable,
Expand Down
Loading
Loading