Skip to content

Instantly share code, notes, and snippets.

@SuperPenguin
Created July 5, 2022 12:33
Show Gist options
  • Save SuperPenguin/840693859a160552ee8aa959d30d8a77 to your computer and use it in GitHub Desktop.
Save SuperPenguin/840693859a160552ee8aa959d30d8a77 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _frameAreaController = FrameAreaController();
@override
void dispose() {
_frameAreaController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrameArea(
controller: _frameAreaController,
child: Frame(
id: 'root',
child: Row(
children: [
Expanded(
child: Frame(
id: 'left-0',
child: Column(
children: const [
Expanded(
child: Center(
child: Frame(
id: 'top',
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('Top'),
),
),
),
),
Expanded(
child: Center(
child: Frame(
id: 'bottom',
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('Bottom'),
),
),
),
),
],
),
),
),
const Expanded(
child: Center(
child: Frame(
id: 'right',
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('Right'),
),
),
),
),
],
),
),
),
);
}
}
class FrameAreaController with ChangeNotifier {
List<HoveredFrame> _hoveredFrames = [];
List<HoveredFrame> get hoveredFrames => _hoveredFrames;
void add(String id, int depth) {
_hoveredFrames = [
..._hoveredFrames,
HoveredFrame(
id: id,
depth: depth,
),
];
_updateHovered();
notifyListeners();
}
void remove(String id) {
_hoveredFrames = [
for (final f in _hoveredFrames)
if (f.id != id) f,
];
_updateHovered();
notifyListeners();
}
void _updateHovered() {
if (_hoveredFrames.isEmpty) {
_hovered = null;
return;
}
if (_hoveredFrames.length == 1) {
_hovered = _hoveredFrames.first;
return;
}
final iterator = _hoveredFrames.iterator;
iterator.moveNext();
var target = iterator.current;
while (iterator.moveNext()) {
final i = iterator.current;
if (i.depth > target.depth) {
target = i;
}
}
_hovered = target;
}
HoveredFrame? _hovered;
String? get hoveredId => _hovered?.id;
void setSelected(String id) {
if (_selectedId != id) {
_selectedId = id;
notifyListeners();
}
}
void removeSelected(String id) {
if (_selectedId == id) {
_selectedId = null;
notifyListeners();
}
}
String? _selectedId;
String? get selectedId => _selectedId;
}
class HoveredFrame {
final String id;
final int depth;
HoveredFrame({
required this.id,
required this.depth,
});
}
class FrameArea extends StatelessWidget {
const FrameArea({
super.key,
required this.controller,
required this.child,
});
final FrameAreaController controller;
final Widget child;
@override
Widget build(BuildContext context) {
return _InheritedController(
controller: controller,
child: child,
);
}
static FrameAreaController read(BuildContext context) {
final element =
context.getElementForInheritedWidgetOfExactType<_InheritedController>();
final widget = element?.widget as _InheritedController;
return widget.controller;
}
static FrameAreaController watch(BuildContext context) {
final widget =
context.dependOnInheritedWidgetOfExactType<_InheritedController>();
return widget?.controller as FrameAreaController;
}
}
class _InheritedController extends InheritedNotifier {
const _InheritedController({
required this.controller,
required super.child,
}) : super(notifier: controller);
final FrameAreaController controller;
}
class Frame extends StatefulWidget {
const Frame({
super.key,
required this.child,
required this.id,
});
final Widget child;
final String id;
@override
State<Frame> createState() => _FrameState();
}
class _FrameState extends State<Frame> {
@override
void deactivate() {
FrameArea.read(context).removeSelected(widget.id);
FrameArea.read(context).remove(widget.id);
super.deactivate();
}
@override
Widget build(BuildContext context) {
final parentFrameDepth = _FrameDepth.of(context);
final thisFrameDepth = parentFrameDepth != null ? parentFrameDepth + 1 : 0;
final isHovered = FrameArea.watch(context).hoveredId == widget.id;
final isSelected = FrameArea.watch(context).selectedId == widget.id;
return MouseRegion(
onEnter: (event) {
FrameArea.read(context).add(widget.id, thisFrameDepth);
},
onExit: (event) {
FrameArea.read(context).remove(widget.id);
},
child: GestureDetector(
onTap: () {
if (isSelected) {
FrameArea.read(context).removeSelected(widget.id);
} else {
FrameArea.read(context).setSelected(widget.id);
}
},
child: _FrameDepth(
depth: thisFrameDepth,
child: DecoratedBox(
position: DecorationPosition.foreground,
decoration: BoxDecoration(
border: isHovered || isSelected
? Border.all(
color: isSelected ? Colors.red : Colors.blue,
width: 1,
)
: Border.all(color: Colors.transparent, width: 0.0),
),
child: widget.child,
),
),
),
);
}
}
class _FrameDepth extends InheritedWidget {
const _FrameDepth({
required this.depth,
required super.child,
});
final int depth;
@override
bool updateShouldNotify(_FrameDepth oldWidget) {
return oldWidget.depth != depth;
}
static int? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_FrameDepth>()?.depth;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment