diff --git a/CMakePresets.json b/CMakePresets.json index 853e7e6d3..46203e06e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -129,9 +129,24 @@ "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" } }, + { + "name": "dev-flutter-asan", + "description": "Builds the flutter frontend (ASAN)", + "binaryDir": "${sourceDir}/build-dev-flutter-asan", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "KDDockWidgets_WERROR": "ON", + "KDDockWidgets_USE_LLD": "ON", + "KDDockWidgets_DEVELOPER_MODE": "ON" + }, + "inherits": [ + "flutter-base", + "asan-base" + ] + }, { "name": "dev-flutter", - "description": "Builds the KDDW layouting for usage with flutter", + "description": "Builds the flutter frontend", "binaryDir": "${sourceDir}/build-dev-flutter", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", 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..960f16bb5 100644 --- a/src/flutter/dart/lib/models/Group.dart +++ b/src/flutter/dart/lib/models/Group.dart @@ -43,13 +43,12 @@ class Group extends GeometryItem implements ffi.Finalizable, ItemWithTitleBar { List items = []; late final TitleBar titlebar; - final ffi.Pointer _hostCpp; late final ffi.Pointer guestCpp; final titleChanged = Signal0(); DropArea dropArea; - Group(this.dropArea, {super.geometry}) : _hostCpp = dropArea.hostPtr { + Group(this.dropArea, {super.geometry}) { titlebar = TitleBar(this); final callbackPointer = ffi.Pointer.fromFunction< @@ -58,7 +57,7 @@ class Group extends GeometryItem implements ffi.Finalizable, ItemWithTitleBar { groupInCtor = this; guestCpp = Bindings.instance.nativeLibrary - .create_guest(this._hostCpp.cast(), callbackPointer); + .create_guest(dropArea.hostPtr.cast(), callbackPointer); groupInCtor = null; _instances[guestCpp.address] = WeakReference(this); @@ -160,8 +159,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/lib/private/kddw_bindings.dart b/src/flutter/dart/lib/private/kddw_bindings.dart index 4d537cfc5..3cf75ebfc 100644 --- a/src/flutter/dart/lib/private/kddw_bindings.dart +++ b/src/flutter/dart/lib/private/kddw_bindings.dart @@ -164,6 +164,23 @@ class NativeLibrary { late final _delete_guest = _delete_guestPtr.asFunction)>(); + void set_guest_host( + ffi.Pointer host, + ffi.Pointer guest, + ) { + return _set_guest_host( + host, + guest, + ); + } + + late final _set_guest_hostPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Pointer)>>('set_guest_host'); + late final _set_guest_host = _set_guest_hostPtr.asFunction< + void Function(ffi.Pointer, ffi.Pointer)>(); + void insert_item( ffi.Pointer host, ffi.Pointer guest, 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); + }); + }); +} diff --git a/src/flutter/kddw_bindings.cpp b/src/flutter/kddw_bindings.cpp index 923f10df0..1760ad8c0 100644 --- a/src/flutter/kddw_bindings.cpp +++ b/src/flutter/kddw_bindings.cpp @@ -98,6 +98,7 @@ class Guest : public KDDockWidgets::Core::LayoutingGuest return; _layoutingHost = parent; + _item->setHost(parent); } Core::LayoutingHost *host() const override @@ -242,6 +243,14 @@ void delete_guest(void *guest) delete reinterpret_cast(guest); } +void set_guest_host(void *_host, void *_guest) +{ + auto host = reinterpret_cast(_host); + auto guest = reinterpret_cast(_guest); + + guest->setHost(host); +} + void validate_enum(int location) { auto loc = static_cast(location); diff --git a/src/flutter/kddw_bindings.h b/src/flutter/kddw_bindings.h index 8db5e1c82..c1f7ecf39 100644 --- a/src/flutter/kddw_bindings.h +++ b/src/flutter/kddw_bindings.h @@ -26,6 +26,7 @@ DOCKS_EXPORT void *create_host(); DOCKS_EXPORT void delete_host(void *host); DOCKS_EXPORT void *create_guest(void *host, void (*callback)(void *guest, int x, int y, int width, int height, int is_visible)); DOCKS_EXPORT void delete_guest(void *host); +DOCKS_EXPORT void set_guest_host(void *host, void *guest); DOCKS_EXPORT void insert_item(void *host, void *guest, int location); DOCKS_EXPORT void insert_item_relative_to(void *host, void *guest, void *relativeToGuest, int location); DOCKS_EXPORT void remove_guest(void *host, void *guest);