Files
ux/lib/src/anim/pane.dart
agra d68a2978eb ux: bulk WIP — UxPlugin→XPlugin rename + new anim/core/navi/reactive packages
Catch-all commit for outstanding pre-existing local changes. Mixes
several themes that would normally be split:

- Rename: UxPlugin → XPlugin across iOS, macOS, Android registrants.
- New top-level packages under lib/src/: anim/ (animated values,
  panes, sheets, dock, measured), core/ (Emitter, ReactiveBuilder
  scaffolding, presenter/widget/value/dispose primitives), navi/
  (Screen/ScreenStack/Router/hero/transitions), reactive/.
- Edits across existing plugins (clipboard, crash, file, gallery,
  keyboard, scanner, sensor, url) to align with the new core.
- Test updates and CHANGELOG/README touches accompanying the above.
2026-05-21 08:58:07 +03:00

385 lines
9.4 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
import 'package:ux/src/core/core.dart';
/// WIP
abstract class Pane with Emitter {
Pane();
final Map<Object?, Widget> widgets = {};
final Map<Object?, Element> elements = {};
final Map<Object?, RenderBox> renderBoxes = {};
// ready to manage children
PaneElement? element;
PaneRender? renderer;
bool get needsCompositing => renderer?.needsCompositing ?? false;
Size get size => renderer!.size;
BoxConstraints get constraints => renderer!.constraints;
void markNeedsLayout() => renderer?.markNeedsLayout();
void markNeedsPaint() => renderer?.markNeedsPaint();
void attachPipeline(PipelineOwner owner) {
for (final child in renderBoxes.values) {
if (child.owner == null) {
child.attach(owner);
}
}
}
void detachPipeline() {
for (final child in renderBoxes.values) {
child.detach();
}
}
void visitChildrenRender(RenderObjectVisitor visitor) =>
renderBoxes.values.forEach(visitor);
void setupParentData(covariant RenderObject child, covariant Object? slot);
List<DiagnosticsNode> debugDescribeChildren() => const [];
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {}
bool hitTestChildren(BoxHitTestResult result, Offset position) => false;
bool hitTestSelf(Offset position) => false;
/// [constraints] are available
/// [size] must be set
void performLayout();
void paint(PaintingContext context, Offset offset);
void insertRenderObjectChild(
PaneElement paneElement, covariant RenderBox child, Object? slot) {
setupParentData(child, slot);
renderBoxes[slot] = child;
renderer?.adoptChild(child);
}
void moveRenderObjectChild(PaneElement paneElement, RenderObject child,
Object? oldSlot, Object? newSlot) {}
void removeRenderObjectChild(
PaneElement paneElement, RenderObject child, Object? slot) {
renderBoxes.remove(slot);
renderer?.dropChild(child);
}
void visitChildrenElement(ElementVisitor visitor) =>
elements.values.forEach(visitor);
Widget itemBuilder(BuildContext context, Object? slot);
RenderBox? invokeLayoutUpsert(Object? slot) {
if (element == null) return null;
RenderBox? child = renderBoxes[slot];
if (child != null) return child;
renderer?.invokeLayoutCallback((constraints) {
child = buildChild(slot);
});
return child;
}
void invokeLayoutRemove(Object? slot) {
if (element == null) return;
renderer?.invokeLayoutCallback((constraints) {
removeSlotElement(slot);
});
}
void removeSlotElement(Object? slot) {
final element = this.element;
if (element == null) return;
elements.remove(slot)?.pipe(element.deactivateChild);
widgets.remove(slot);
}
void mountElement(Element? parent, Object? newSlot, PaneElement element) {
this.element = element;
}
void unmountElement() {
for (final child in renderBoxes.values) {
renderer?.dropChild(child);
}
renderBoxes.clear();
elements.clear();
widgets.clear();
element = null;
}
RenderBox? buildChild(Object? slot) {
final element = this.element;
if (element == null) return null;
element.owner!.buildScope(element, () {
Widget newWidget;
try {
newWidget = itemBuilder(element, slot);
} catch (e) {
return;
}
final oldElement = elements[slot];
final oldWidget = widgets[slot];
if (oldElement == null) {
final newElement = element.inflateWidget(newWidget, slot);
elements[slot] = newElement;
widgets[slot] = newWidget;
} else if (oldWidget != null && Widget.canUpdate(oldWidget, newWidget)) {
oldElement.update(newWidget);
widgets[slot] = newWidget;
} else {
element.deactivateChild(oldElement);
final newElement = element.inflateWidget(newWidget, slot);
elements[slot] = newElement;
widgets[slot] = newWidget;
}
});
return elements[slot]?.renderObject as RenderBox?;
}
void forgetChildElement(Element child) {
elements.removeWhere((_, value) => value == child);
}
}
class PaneView extends StatelessWidget {
const PaneView({
required this.controller,
required this.scrollController,
this.scrollPhysics,
super.key,
});
final Pane controller;
final ScrollController scrollController;
final ScrollPhysics? scrollPhysics;
Widget buildViewport(BuildContext context, ViewportOffset offset) {
return PaneViewport(controller: controller);
}
@override
Widget build(BuildContext context) {
return Scrollable(
controller: scrollController,
physics: scrollPhysics,
viewportBuilder: buildViewport,
);
}
}
class PaneViewport extends RenderObjectWidget {
const PaneViewport({
required this.controller,
super.key,
});
final Pane controller;
@override
RenderObjectElement createElement() {
return PaneElement(this, controller);
}
@override
RenderObject createRenderObject(BuildContext context) {
return PaneRender(controller: controller);
}
@override
void updateRenderObject(
BuildContext context, covariant PaneRender renderObject) {
renderObject.controller = controller;
}
}
class PaneElement extends RenderObjectElement {
PaneElement(super.widget, this._controller);
Pane _controller;
Pane get controller => _controller;
@override
void insertRenderObjectChild(
covariant RenderBox child, covariant Object? slot) {
controller.insertRenderObjectChild(this, child, slot);
}
@override
void moveRenderObjectChild(covariant RenderObject child,
covariant Object? oldSlot, covariant Object? newSlot) {
controller.moveRenderObjectChild(this, child, oldSlot, newSlot);
}
@override
void removeRenderObjectChild(
covariant RenderObject child, covariant Object? slot) {
controller.removeRenderObjectChild(this, child, slot);
}
@override
void visitChildren(ElementVisitor visitor) =>
controller.visitChildrenElement(visitor);
@override
void deactivateChild(Element child) => super.deactivateChild(child);
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
controller.mountElement(parent, newSlot, this);
}
@override
void unmount() {
controller.unmountElement();
super.unmount();
}
@override
void update(covariant PaneViewport newWidget) {
final oldController = _controller;
final newController = newWidget.controller;
if (oldController != newController) {
oldController.unmountElement();
_controller = newController;
newController.mountElement(null, null, this);
}
super.update(newWidget);
}
@override
void forgetChild(Element child) {
controller.forgetChildElement(child);
super.forgetChild(child);
}
@override
Element inflateWidget(Widget newWidget, Object? newSlot) =>
super.inflateWidget(newWidget, newSlot);
@override
PaneRender get renderObject => super.renderObject as PaneRender;
@override
PaneViewport get widget => super.widget as PaneViewport;
}
class PaneRender extends RenderBox {
PaneRender({
required Pane controller,
}) : _controller = controller;
Pane _controller;
Pane get controller => _controller;
set controller(Pane value) {
if (_controller == value) return;
if (attached) {
_controller.detachPipeline();
_controller.renderer = null;
}
_controller = value;
if (attached) {
_controller.attachPipeline(owner!);
_controller.renderer = this;
markNeedsLayout();
}
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_controller.attachPipeline(owner);
_controller.renderer = this;
}
@override
void detach() {
super.detach();
_controller.detachPipeline();
_controller.renderer = null;
}
@override
void adoptChild(RenderObject child) => super.adoptChild(child);
@override
void dropChild(RenderObject child) => super.dropChild(child);
@override
void setupParentData(covariant RenderObject child) {
//no-op
}
@override
bool get sizedByParent => true;
@override
void performResize() {
size = constraints.biggest;
}
@override
void performLayout() {
if (controller.renderer == null) return;
controller.performLayout();
}
@override
void paint(PaintingContext context, Offset offset) {
if (controller.renderer == null) return;
controller.paint(context, offset);
}
@override
bool hitTestSelf(Offset position) => controller.hitTestSelf(position);
@override
void invokeLayoutCallback<T extends Constraints>(
LayoutCallback<T> callback) =>
super.invokeLayoutCallback(callback);
@override
bool hitTestChildren(
BoxHitTestResult result, {
required Offset position,
}) =>
controller.hitTestChildren(result, position);
@override
void visitChildren(RenderObjectVisitor visitor) =>
controller.visitChildrenRender(visitor);
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
assert(child.parent == this);
controller.applyPaintTransform(child, transform);
}
@override
List<DiagnosticsNode> debugDescribeChildren() =>
controller.debugDescribeChildren();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
controller.debugFillProperties(properties);
}
}