Skip to content

Commit 7d525b9

Browse files
ddd
1 parent 5bf8275 commit 7d525b9

25 files changed

Lines changed: 736 additions & 269 deletions

lib/components/bubble/bubble_view.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ class _BubbleViewState extends State<BubbleView> {
133133
}
134134
_syncTicker();
135135
final GlossBubblePreviewTimeline timeline = _timelineFor(doc);
136-
final List<GlossBubblePreviewBubble> bubbles = timeline.bubblesAt(_nowMs());
136+
final int nowMs = _nowMs();
137+
final List<GlossBubblePreviewBubble> bubbles = timeline.bubblesAt(nowMs);
138+
final List<double> offset = doc.offset;
137139

138140
return dom.div(classes: 'hui-bubble-stage', <Widget>[
139141
dom.div(classes: 'hui-bubble-sky', <Widget>[
@@ -147,7 +149,9 @@ class _BubbleViewState extends State<BubbleView> {
147149
styles: dom.Styles(
148150
raw: <String, String>{
149151
'bottom':
150-
'${(bubble.offsetY * _pixelsPerBlock).toStringAsFixed(1)}px',
152+
'${((bubble.offsetY + offset[1]) * _pixelsPerBlock).toStringAsFixed(1)}px',
153+
'left':
154+
'calc(50% + ${(offset[0] * _pixelsPerBlock).toStringAsFixed(1)}px)',
151155
'opacity': bubble.remainingMs < 400
152156
? (bubble.remainingMs / 400).toStringAsFixed(2)
153157
: '1',
@@ -157,7 +161,9 @@ class _BubbleViewState extends State<BubbleView> {
157161
GlossTextLine(
158162
render: renderGlossLine(
159163
doc.effectivePrefix + bubble.text,
164+
animations: _store.workspaceAnimations,
160165
emoji: _store.workspaceEmoji,
166+
nowMs: nowMs,
161167
),
162168
),
163169
],
@@ -192,6 +198,7 @@ class _BubbleViewState extends State<BubbleView> {
192198
String _readout(GlossBubbleStyleDoc doc) {
193199
final List<String> parts = <String>[
194200
'wrap ${doc.effectiveWordWrapChars} chars',
201+
'offset ${doc.offset.map((double value) => value.toStringAsFixed(1)).join(', ')}',
195202
'stagger ${doc.effectiveLineStaggerTicks} ticks '
196203
'(${doc.effectiveLineStaggerTicks * 50} ms)',
197204
'alive ${doc.effectiveMaxAliveMs} ms',

lib/components/emoji/emoji_view.dart

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,22 +135,19 @@ class _EmojiViewState extends State<EmojiView> {
135135
Widget _chatPreview(GlossEmojiDoc doc, String openId) {
136136
final String token = ':$openId:';
137137
final String sample = doc.trigger.isEmpty
138-
? 'Nice one $token'
139-
: 'Nice one $token ${doc.trigger}';
140-
final String substituted = glossApplyEmoji(
141-
sample,
142-
_store.workspaceEmoji,
143-
);
138+
? 'SwiftSwamp smells >.< $token'
139+
: 'SwiftSwamp smells >.< $token ${doc.trigger}';
140+
final String substituted = glossApplyEmoji(sample, _store.workspaceEmoji);
144141
return dom.div(classes: 'hui-emoji-chat', <Widget>[
145142
dom.div(classes: 'hui-emoji-chat-line is-raw', <Widget>[
146143
const dom.span(classes: 'hui-emoji-chat-speaker', <Widget>[
147-
Text('<Steve>'),
144+
Text('<Magic_Psycho>'),
148145
]),
149146
Text(' $sample'),
150147
]),
151148
dom.div(classes: 'hui-emoji-chat-line', <Widget>[
152149
const dom.span(classes: 'hui-emoji-chat-speaker', <Widget>[
153-
Text('<Steve>'),
150+
Text('<Magic_Psycho>'),
154151
]),
155152
Text(' $substituted'),
156153
]),

lib/components/hologram/hologram_view.dart

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class _HologramViewState extends State<HologramView> {
4949

5050
OrbitCamera _camera = const OrbitCamera();
5151
String? _cameraDocId;
52+
PVec3? _cameraFocus;
5253
bool _dragging = false;
5354
double _lastX = 0;
5455
double _lastY = 0;
@@ -89,14 +90,25 @@ class _HologramViewState extends State<HologramView> {
8990
setState(_syncCameraToDocument);
9091
}
9192

92-
/// Re-aims the default orbit when a DIFFERENT document arrives; edits to
93-
/// the open one leave the author's camera alone.
93+
/// A different document gets its default orbit. Edits to the open document
94+
/// preserve the author's orbit but carry its target with a moved anchor.
9495
void _syncCameraToDocument() {
9596
final String? activeId = _store.workspace.activeId;
96-
if (activeId == _cameraDocId) return;
97-
_cameraDocId = activeId;
9897
final GlossHologramDoc? doc = _store.hologramDoc;
99-
if (doc != null) _camera = hologramDefaultCamera(doc);
98+
if (doc == null) {
99+
_cameraDocId = activeId;
100+
_cameraFocus = null;
101+
return;
102+
}
103+
final OrbitCamera defaultCamera = hologramDefaultCamera(doc);
104+
final PVec3? previousFocus = _cameraFocus;
105+
if (activeId != _cameraDocId || previousFocus == null) {
106+
_camera = defaultCamera;
107+
} else {
108+
_camera = reframeHologramCamera(_camera, previousFocus, doc);
109+
}
110+
_cameraDocId = activeId;
111+
_cameraFocus = defaultCamera.target;
100112
}
101113

102114
void _observeSize() {
@@ -188,7 +200,10 @@ class _HologramViewState extends State<HologramView> {
188200
void _resetCamera() {
189201
final GlossHologramDoc? doc = _store.hologramDoc;
190202
if (doc == null) return;
191-
setState(() => _camera = hologramDefaultCamera(doc));
203+
setState(() {
204+
_camera = hologramDefaultCamera(doc);
205+
_cameraFocus = _camera.target;
206+
});
192207
}
193208

194209
@override

lib/components/inspector/icon_editor.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@ class _ImageIconEditor extends StatelessWidget {
829829
ImageUploadButton(
830830
images: images,
831831
inputId: inputId,
832+
label: 'Upload PNG, WebP or GIF',
832833
onAdded: (List<String> paths) {
833834
if (paths.isNotEmpty) _setPath(paths.first);
834835
},
@@ -1052,6 +1053,7 @@ class _AnimatedIconEditorState extends State<_AnimatedIconEditor> {
10521053
ImageUploadButton(
10531054
images: component.images,
10541055
inputId: component.inputId,
1056+
label: 'Upload image or GIF frames',
10551057
onAdded: _addFrames,
10561058
),
10571059
]);

lib/components/inspector/tablist_inspector.dart

Lines changed: 90 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import 'animation_reference_picker.dart';
1919
import 'field_help.dart';
2020
import 'inspector_widgets.dart';
2121

22-
/// The sample players the live preview resolves, mirroring the surface.
2322
const List<({String name, String? group, bool op})> _samples =
2423
<({String name, String? group, bool op})>[
25-
(name: 'Steve', group: 'default', op: false),
26-
(name: 'Herobrine', group: 'admin', op: true),
27-
(name: 'Alex', group: 'vip', op: false),
24+
(name: 'Cyberpwn', group: 'owner', op: true),
25+
(name: 'Magic_Psycho', group: 'developer', op: false),
26+
(name: 'SwiftSwamp', group: 'moderator', op: false),
27+
(name: 'Puretie', group: 'vip', op: false),
2828
];
2929

3030
class TablistInspector extends StatefulWidget {
@@ -61,25 +61,25 @@ class _TablistInspectorState extends State<TablistInspector> {
6161
]);
6262
}
6363

64-
Widget _header(GlossTablistDoc doc) =>
65-
dom.div(classes: 'hui-inspector-headgroup', <Widget>[
66-
dom.div(classes: 'hui-inspector-header is-tablist', <Widget>[
67-
const HuiEyebrow('Tablist'),
68-
dom.div(classes: 'hui-inspector-title-row', <Widget>[
69-
dom.h2(classes: 'hui-inspector-title', <Widget>[
70-
Text(_store.menuId),
71-
]),
72-
const HuiFieldHelp('tablist.id'),
73-
]),
74-
]),
75-
dom.p(classes: 'hui-inspector-lede', <Widget>[
76-
Text(
77-
'The tab screen: header and footer over the player grid, and '
78-
'per-group list names. Revision ${doc.revision} is server-owned '
79-
'and travels with the file.',
80-
),
64+
Widget _header(GlossTablistDoc doc) => dom.div(
65+
classes: 'hui-inspector-headgroup',
66+
<Widget>[
67+
dom.div(classes: 'hui-inspector-header is-tablist', <Widget>[
68+
const HuiEyebrow('Tablist'),
69+
dom.div(classes: 'hui-inspector-title-row', <Widget>[
70+
dom.h2(classes: 'hui-inspector-title', <Widget>[Text(_store.menuId)]),
71+
const HuiFieldHelp('tablist.id'),
8172
]),
82-
]);
73+
]),
74+
dom.p(classes: 'hui-inspector-lede', <Widget>[
75+
Text(
76+
'The tab screen: header and footer over the player grid, and '
77+
'per-group list names. Revision ${doc.revision} is server-owned '
78+
'and travels with the file.',
79+
),
80+
]),
81+
],
82+
);
8383

8484
Widget _pipelinePreview(String text) =>
8585
dom.div(classes: 'hui-hologram-line-preview', <Widget>[
@@ -129,13 +129,12 @@ class _TablistInspectorState extends State<TablistInspector> {
129129
size: ComponentSize.sm,
130130
fullWidth: true,
131131
placeholder: '&d&lGloss',
132-
onInput: (String value) => _store.mutateTablist(
133-
'tablist header',
134-
(GlossTablistDoc edited) {
135-
edited.header = value;
136-
edited.absentKeys.remove('header');
137-
},
138-
),
132+
onInput: (String value) => _store.mutateTablist('tablist header', (
133+
GlossTablistDoc edited,
134+
) {
135+
edited.header = value;
136+
edited.absentKeys.remove('header');
137+
}),
139138
onFocus: () => _focused = _Focus.header,
140139
attributes: const <String, String>{
141140
'autocomplete': 'off',
@@ -156,13 +155,12 @@ class _TablistInspectorState extends State<TablistInspector> {
156155
size: ComponentSize.sm,
157156
fullWidth: true,
158157
placeholder: '&7VolmitSoftware.com',
159-
onInput: (String value) => _store.mutateTablist(
160-
'tablist footer',
161-
(GlossTablistDoc edited) {
162-
edited.footer = value;
163-
edited.absentKeys.remove('footer');
164-
},
165-
),
158+
onInput: (String value) => _store.mutateTablist('tablist footer', (
159+
GlossTablistDoc edited,
160+
) {
161+
edited.footer = value;
162+
edited.absentKeys.remove('footer');
163+
}),
166164
onFocus: () => _focused = _Focus.footer,
167165
attributes: const <String, String>{
168166
'autocomplete': 'off',
@@ -206,19 +204,17 @@ class _TablistInspectorState extends State<TablistInspector> {
206204
variant: ButtonVariant.outline,
207205
size: ButtonSize.sm,
208206
icon: ArcaneIcon.plus(size: IconSize.sm),
209-
onPressed: () => _store.mutateTablist(
210-
'add format',
211-
(GlossTablistDoc edited) {
212-
String key = 'group';
213-
int suffix = 2;
214-
while (edited.nameFormats.containsKey(key)) {
215-
key = 'group-$suffix';
216-
suffix++;
217-
}
218-
edited.nameFormats[key] = r'&7$player';
219-
edited.absentKeys.remove('nameFormats');
220-
},
221-
),
207+
onPressed: () =>
208+
_store.mutateTablist('add format', (GlossTablistDoc edited) {
209+
String key = 'group';
210+
int suffix = 2;
211+
while (edited.nameFormats.containsKey(key)) {
212+
key = 'group-$suffix';
213+
suffix++;
214+
}
215+
edited.nameFormats[key] = r'&7$player';
216+
edited.absentKeys.remove('nameFormats');
217+
}),
222218
child: const Text('Add format'),
223219
),
224220
HuiInlineIssues(_issuesFor(r'$.nameFormats')),
@@ -239,9 +235,7 @@ class _TablistInspectorState extends State<TablistInspector> {
239235
'title': '$hint Click to append to the focused header/footer field.',
240236
'aria-label': 'Insert $token',
241237
},
242-
events: <String, EventCallback>{
243-
'click': (Object? _) => _insert(token),
244-
},
238+
events: <String, EventCallback>{'click': (Object? _) => _insert(token)},
245239
<Widget>[Text(token)],
246240
);
247241

@@ -252,25 +246,23 @@ class _TablistInspectorState extends State<TablistInspector> {
252246
value: key,
253247
size: ComponentSize.sm,
254248
placeholder: 'vip',
255-
onInput: (String next) => _store.mutateTablist(
256-
'rename format',
257-
(GlossTablistDoc edited) {
258-
if (!edited.nameFormats.containsKey(key) ||
259-
edited.nameFormats.containsKey(next)) {
260-
return;
261-
}
262-
// Rebuild in place to keep the entry's position — the file is
263-
// insertion-ordered like the LinkedHashMap the plugin builds.
264-
final Map<String, String> rebuilt = <String, String>{
265-
for (final MapEntry<String, String> entry
266-
in edited.nameFormats.entries)
267-
(entry.key == key ? next : entry.key): entry.value,
268-
};
269-
edited.nameFormats
270-
..clear()
271-
..addAll(rebuilt);
272-
},
273-
),
249+
onInput: (String next) =>
250+
_store.mutateTablist('rename format', (GlossTablistDoc edited) {
251+
if (!edited.nameFormats.containsKey(key) ||
252+
edited.nameFormats.containsKey(next)) {
253+
return;
254+
}
255+
// Rebuild in place to keep the entry's position — the file is
256+
// insertion-ordered like the LinkedHashMap the plugin builds.
257+
final Map<String, String> rebuilt = <String, String>{
258+
for (final MapEntry<String, String> entry
259+
in edited.nameFormats.entries)
260+
(entry.key == key ? next : entry.key): entry.value,
261+
};
262+
edited.nameFormats
263+
..clear()
264+
..addAll(rebuilt);
265+
}),
274266
attributes: const <String, String>{
275267
'autocomplete': 'off',
276268
'spellcheck': 'false',
@@ -282,14 +274,12 @@ class _TablistInspectorState extends State<TablistInspector> {
282274
size: ComponentSize.sm,
283275
fullWidth: true,
284276
placeholder: r'&7$player',
285-
onInput: (String next) => _store.mutateTablist(
286-
'edit format',
287-
(GlossTablistDoc edited) {
288-
if (edited.nameFormats.containsKey(key)) {
289-
edited.nameFormats[key] = next;
290-
}
291-
},
292-
),
277+
onInput: (String next) =>
278+
_store.mutateTablist('edit format', (GlossTablistDoc edited) {
279+
if (edited.nameFormats.containsKey(key)) {
280+
edited.nameFormats[key] = next;
281+
}
282+
}),
293283
attributes: <String, String>{
294284
'autocomplete': 'off',
295285
'spellcheck': 'false',
@@ -307,24 +297,28 @@ class _TablistInspectorState extends State<TablistInspector> {
307297
]);
308298
}
309299

310-
Widget _livePreview(GlossTablistDoc doc) => dom.div(
311-
classes: 'hui-tablist-live-preview',
312-
<Widget>[
313-
const HuiEyebrow('Live preview'),
314-
for (final ({String name, String? group, bool op}) sample in _samples)
315-
dom.div(classes: 'hui-tablist-live-row', <Widget>[
316-
dom.span(classes: 'hui-tablist-live-meta', <Widget>[
317-
Text(
318-
'${sample.name} — '
319-
'${sample.op ? 'op, ' : ''}group ${sample.group ?? 'none'}',
320-
),
321-
]),
322-
dom.span(classes: 'hui-tablist-live-name', <Widget>[
323-
GlossTextLine(render: renderGlossLine(_resolved(doc, sample))),
300+
Widget _livePreview(GlossTablistDoc doc) =>
301+
dom.div(classes: 'hui-tablist-live-preview', <Widget>[
302+
const HuiEyebrow('Live preview'),
303+
for (final ({String name, String? group, bool op}) sample in _samples)
304+
dom.div(classes: 'hui-tablist-live-row', <Widget>[
305+
dom.span(classes: 'hui-tablist-live-meta', <Widget>[
306+
Text(
307+
'${sample.name} — '
308+
'${sample.op ? 'op, ' : ''}group ${sample.group ?? 'none'}',
309+
),
310+
]),
311+
dom.span(classes: 'hui-tablist-live-name', <Widget>[
312+
GlossTextLine(
313+
render: renderGlossLine(
314+
_resolved(doc, sample),
315+
animations: _store.workspaceAnimations,
316+
emoji: _store.workspaceEmoji,
317+
),
318+
),
319+
]),
324320
]),
325-
]),
326-
],
327-
);
321+
]);
328322

329323
String _resolved(
330324
GlossTablistDoc doc,

0 commit comments

Comments
 (0)