Created
July 5, 2022 12:33
-
-
Save SuperPenguin/840693859a160552ee8aa959d30d8a77 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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