diff --git a/src/flutter/dart/lib/models/DropArea.dart b/src/flutter/dart/lib/models/DropArea.dart index 0fc8f84a8..253f4e49a 100644 --- a/src/flutter/dart/lib/models/DropArea.dart +++ b/src/flutter/dart/lib/models/DropArea.dart @@ -84,11 +84,13 @@ class DropArea implements ffi.Finalizable { void _addGroup(Group group) { _groups.add(group); + group.dropArea = this; layoutChanged.emit(); } void _removeGroup(Group group) { _groups.remove(group); + Bindings.instance.nativeLibrary .remove_guest(_hostCpp.cast(), group.guestCpp.cast()); layoutChanged.emit(); @@ -145,6 +147,10 @@ class DropArea implements ffi.Finalizable { return false; } + bool containsGroup(Group group) { + return _groups.contains(group); + } + List get groups { return _groups; } diff --git a/src/flutter/dart/lib/models/FloatingItem.dart b/src/flutter/dart/lib/models/FloatingItem.dart index e4bb11d28..bbe597e77 100644 --- a/src/flutter/dart/lib/models/FloatingItem.dart +++ b/src/flutter/dart/lib/models/FloatingItem.dart @@ -34,9 +34,34 @@ class FloatingItem implements ItemWithTitleBar { return dropArea.groups.length > 1; } + bool containsGroup(Group group) { + return dropArea.containsGroup(group); + } + + void addGroup(Group group) { + dropArea._addGroup(group); + } + @override void close() { _groupCountChangedConnection.disconnect(); DockRegistry.instance.removeFloatingItem(this); } + + @override + bool isFloating() { + // A floating window is floating + return true; + } + + @override + void unfloat() { + // attach floating window into main window again + } + + @override + void float() { + // a floating window is already floating + throw "unreachable"; + } } diff --git a/src/flutter/dart/lib/models/Group.dart b/src/flutter/dart/lib/models/Group.dart index 04e4ab5df..41614db5a 100644 --- a/src/flutter/dart/lib/models/Group.dart +++ b/src/flutter/dart/lib/models/Group.dart @@ -160,8 +160,54 @@ class Group extends GeometryItem implements ffi.Finalizable, ItemWithTitleBar { } } + /// Returns the floating item this group is in, if any. + /// It might be inside the main window, in which case this returns null + FloatingItem? floatingItem() { + for (var floatingItem in DockRegistry.instance.floatingItems) { + if (floatingItem.containsGroup(this)) return floatingItem; + } + + return null; + } + + /// If false, then it's inside the main window + bool isInFloatingWindow() { + return floatingItem() != null; + } + @override void close() { dropArea._removeGroup(this); } + + /// A group is floating if: + /// It's in a floating window without nesting + /// i.e: it's the only group + /// i.e: it can't be detached into a floating window + @override + bool isFloating() { + final fi = floatingItem(); + if (fi == null) { + return false; + } else { + return fi.dropArea.groups.length == 1; + } + } + + @override + void unfloat() { + // a group is already attached + throw "unreachable"; + } + + @override + void float() { + // transform the group into a floating window + if (isFloating()) return; + + dropArea._removeGroup(this); + + final floatingItem = FloatingItem(); + floatingItem.addGroup(this); + } } diff --git a/src/flutter/dart/lib/models/TitleBar.dart b/src/flutter/dart/lib/models/TitleBar.dart index 44645396a..611bcf66b 100644 --- a/src/flutter/dart/lib/models/TitleBar.dart +++ b/src/flutter/dart/lib/models/TitleBar.dart @@ -15,6 +15,15 @@ part of kddockwidgets; abstract class ItemWithTitleBar { // called when close button on the titlebar is clicked void close(); + + // whether the titlebar is attached to a floating window or group + bool isFloating(); + + // detaches into a floating window + void float(); + + // docks a floating window into the main drop area + void unfloat(); } class TitleBar { @@ -29,7 +38,17 @@ class TitleBar { _itemWithTitleBar.close(); } - void onFloatClicked() {} + void onFloatClicked() { + if (isFloating()) { + _itemWithTitleBar.unfloat(); + } else { + _itemWithTitleBar.float(); + } + } void onMouseEvent(PointerEvent ev) {} + + bool isFloating() { + return _itemWithTitleBar.isFloating(); + } } diff --git a/src/flutter/dart/test/models/floatingitem_test.dart b/src/flutter/dart/test/models/floatingitem_test.dart new file mode 100644 index 000000000..5eb57cee4 --- /dev/null +++ b/src/flutter/dart/test/models/floatingitem_test.dart @@ -0,0 +1,42 @@ +/* + This file is part of KDDockWidgets. + + SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Sérgio Martins + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only + + Contact KDAB at for commercial licensing options. +*/ + +import 'package:flutter_test/flutter_test.dart'; +import 'package:KDDockWidgets/KDDockWidgets.dart'; +import 'package:KDDockWidgets/private/Bindings.dart'; + +void main() { + group('FloatingItem', () { + test('isFloating', () { + var dropArea = DropArea(); + final dockName = "dock1"; + expect(dropArea.containsDockItem(dockName), false); + var dock = DockItem(uniqueName: dockName); + dropArea.addDockItem(dock, Location.LocationOnTop); + + var group = dropArea.groups.first; + // It's docked, so not floating yet + expect(group.isFloating(), false); + expect(dropArea.groups.length, 1); + expect(group.dropArea, dropArea); + + // Undock: + group.titlebar.onFloatClicked(); + expect(group.isFloating(), true); + expect(dropArea.groups.length, 0); + expect(group.dropArea != dropArea, true); + + // Redock: + // group.titlebar.onFloatClicked(); + // expect(group.isFloating(), false); + }); + }); +}