Skip to content

Commit

Permalink
Improve single frame segment experience
Browse files Browse the repository at this point in the history
  • Loading branch information
gabber235 committed Feb 1, 2024
1 parent daa4871 commit d0c2431
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 33 deletions.
10 changes: 10 additions & 0 deletions app/lib/models/segment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,18 @@ extension SegmentX on Segment {

IntRange get range => IntRange(startFrame, endFrame);

bool get isSingleFrame => maxFrames == 1;

bool contains(int frame) => frame >= startFrame && frame < endFrame;

bool overlaps(int startFrame, int endFrame) =>
contains(startFrame) || contains(endFrame);

String get display {
if (isSingleFrame) {
return "$startFrame";
}

return "[$startFrame, $endFrame]";
}
}
248 changes: 215 additions & 33 deletions app/lib/widgets/components/app/cinematic_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1185,14 +1185,11 @@ class _SegmentWidget extends HookConsumerWidget {
final entryId = ref.read(inspectingEntryIdProvider);
if (entryId == null) return;

showConfirmationDialogue(
context: context,
title: "Delete Segment",
content: "Are you sure you want to delete this segment?",
confirmText: "Delete",
onConfirm: () {
_deleteSegment(ref.passing, entryId, segmentId);
},
deleteSegmentConfirmation(
context,
ref.passing,
entryId,
segmentId,
);
},
),
Expand Down Expand Up @@ -1581,37 +1578,167 @@ class CinematicInspector extends HookConsumerWidget {
_entryContextActionsProvider(inspectingEntry.id),
),
ignoreFields: ref.watch(_ignoreEntryFieldsProvider),
sections: const [
_SegmentSelector(),
],
)
: const EmptyInspector(),
),
);
}
}

class _SegmentSelector extends HookConsumerWidget {
const _SegmentSelector();

@override
Widget build(BuildContext context, WidgetRef ref) {
final entryId = ref.watch(inspectingEntryIdProvider);
if (entryId == null) return const SizedBox.shrink();
final segments = ref.watch(_allSegmentsProvider(entryId));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionTitle(title: "Segments"),
const SizedBox(height: 8),
if (segments.isEmpty) ...[
const Text("No segments"),
] else ...[
for (final segment in segments)
_SegmentSelectorTile(segment: segment),
],
],
);
}
}

class _SegmentSelectorTile extends HookConsumerWidget {
const _SegmentSelectorTile({
required this.segment,
});

final Segment segment;

@override
Widget build(BuildContext context, WidgetRef ref) {
final entryId = ref.watch(inspectingEntryIdProvider);
if (entryId == null) return const SizedBox.shrink();
final color = segment.color;

return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: ContextMenuRegion(
builder: (context) => [
ContextMenuTile.button(
title: "Select",
icon: FontAwesomeIcons.solidSquareCheck,
onTap: () {
ref
.read(inspectingSegmentIdProvider.notifier)
.select(entryId, segment.truePath);
},
),
ContextMenuTile.button(
title: "Duplicate",
icon: FontAwesomeIcons.clone,
onTap: () {
_duplicateSelectedSegment(ref.passing);
},
),
ContextMenuTile.button(
title: "Delete",
icon: FontAwesomeIcons.trash,
color: Theme.of(context).colorScheme.error,
onTap: () {
deleteSegmentConfirmation(
context,
ref.passing,
entryId,
segment.truePath,
);
},
),
],
child: Material(
color: color,
borderRadius: BorderRadius.circular(4),
child: InkWell(
onTap: () {
ref
.read(inspectingSegmentIdProvider.notifier)
.select(entryId, segment.truePath);
},
mouseCursor: SystemMouseCursors.click,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
segment.icon,
size: 16,
color: color.computeLuminance() > 0.5
? Colors.black.withOpacity(0.6)
: Colors.white.withOpacity(0.6),
),
const SizedBox(width: 8),
Text(
"Segment ${segment.display}",
style: TextStyle(
fontSize: 14,
color: color.computeLuminance() > 0.5
? Colors.black
: Colors.white,
),
),
const Spacer(),
Icon(
FontAwesomeIcons.angleRight,
size: 12,
color: color.computeLuminance() > 0.5
? Colors.black.withOpacity(0.5)
: Colors.white.withOpacity(0.5),
),
],
),
),
),
),
),
);
}
}

class _SegmentInspector extends HookConsumerWidget {
const _SegmentInspector({
super.key,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
return const SingleChildScrollView(
final segment = ref.watch(inspectingSegmentProvider);
if (segment == null) return const SizedBox.shrink();
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_InspectorHeader(),
Divider(),
_StartFrameField(),
SizedBox(height: 8),
_EndFrameField(),
SizedBox(height: 8),
_SegmentDurationDisplay(),
Divider(),
_InspectorContents(),
Divider(),
_SegmentOperations(),
SizedBox(height: 30),
const _InspectorHeader(),
const Divider(),
if (segment.isSingleFrame)
const _SingleFrameField()
else ...[
const _StartFrameField(),
const SizedBox(height: 8),
const _EndFrameField(),
const SizedBox(height: 8),
const _SegmentDurationDisplay(),
],
const Divider(),
const _InspectorContents(),
const Divider(),
const _SegmentOperations(),
const SizedBox(height: 30),
],
),
);
Expand Down Expand Up @@ -1678,13 +1805,15 @@ class _FrameField extends HookConsumerWidget {
this.icon,
this.hintText = "",
this.onValidate,
this.onDone,
});

final String path;
final String title;
final IconData? icon;
final String hintText;
final String? Function(int)? onValidate;
final Function(int)? onDone;

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand Down Expand Up @@ -1724,9 +1853,13 @@ class _FrameField extends HookConsumerWidget {
if (errorText != null) return;
}

ref
.read(inspectingEntryDefinitionProvider)
?.updateField(ref.passing, path, newValue);
if (onDone != null) {
onDone?.call(newValue);
} else {
ref
.read(inspectingEntryDefinitionProvider)
?.updateField(ref.passing, path, newValue);
}
},
maxLines: 1,
),
Expand All @@ -1736,6 +1869,46 @@ class _FrameField extends HookConsumerWidget {
}
}

class _SingleFrameField extends HookConsumerWidget {
const _SingleFrameField();

@override
Widget build(BuildContext context, WidgetRef ref) {
final segmentId = ref.watch(inspectingSegmentIdProvider);

if (segmentId == null) return const SizedBox.shrink();

return _FrameField(
title: "Frame",
path: "$segmentId.startFrame",
icon: FontAwesomeIcons.forwardStep,
hintText: "Enter a frame number",
onValidate: (frame) {
final entryId = ref.read(inspectingEntryIdProvider);
if (entryId == null) return "No entry selected";
final segment = ref.read(inspectingSegmentProvider);
if (segment == null) return "No segment selected";

final segments = ref.read(_segmentsProvider(entryId, segmentId.wild()));
if (segments
.where((s) => s.truePath != segmentId)
.any((s) => s.startFrame == frame)) {
return "A segment already exists at this frame";
}
return null;
},
onDone: (frame) {
ref
.read(inspectingEntryDefinitionProvider)
?.updateField(ref.passing, "$segmentId.startFrame", frame);
ref
.read(inspectingEntryDefinitionProvider)
?.updateField(ref.passing, "$segmentId.endFrame", frame + 1);
},
);
}
}

class _StartFrameField extends HookConsumerWidget {
const _StartFrameField();

Expand Down Expand Up @@ -1900,20 +2073,29 @@ class _DeleteSegment extends HookConsumerWidget {
final entryId = ref.read(inspectingEntryIdProvider);
if (entryId == null) return;

showConfirmationDialogue(
context: context,
title: "Delete Segment",
content: "Are you sure you want to delete this segment?",
confirmText: "Delete",
onConfirm: () {
_deleteSegment(ref.passing, entryId, segmentId);
},
);
deleteSegmentConfirmation(context, ref.passing, entryId, segmentId);
},
icon: const FaIcon(FontAwesomeIcons.trash),
label: const Text("Delete Segment"),
color: Theme.of(context).colorScheme.error,
);
}
}

void deleteSegmentConfirmation(
BuildContext context,
PassingRef ref,
String entryId,
String segmentId,
) {
showConfirmationDialogue(
context: context,
title: "Delete Segment",
content: "Are you sure you want to delete this segment?",
confirmText: "Delete",
onConfirm: () {
_deleteSegment(ref, entryId, segmentId);
},
);
}
//</editor-fold>
8 changes: 8 additions & 0 deletions app/lib/widgets/inspector/inspector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,16 @@ class EntryInspector extends HookConsumerWidget {
const EntryInspector({
this.ignoreFields = const [],
this.actions = const [],
this.sections = const [],
super.key,
}) : super();

final List<String> ignoreFields;
final List<ContextMenuTile> actions;

/// Additional sections to display in the inspector.
final List<Widget> sections;

@override
Widget build(BuildContext context, WidgetRef ref) {
final object = ref.watch(
Expand All @@ -140,6 +144,10 @@ class EntryInspector extends HookConsumerWidget {
ignoreFields: ["id", "name", ...ignoreFields],
defaultExpanded: true,
),
for (final section in sections) ...[
const Divider(),
section,
],
const Divider(),
Operations(actions: actions),
const SizedBox(height: 30),
Expand Down

0 comments on commit d0c2431

Please sign in to comment.