add window resize handles for frameless mode
Without OS decorations there are no resize handles. Added 6px edge and 12px corner invisible hit zones around the window perimeter via ClideResizeBorder. Each zone changes the cursor and calls gtk_window_begin_resize_drag via the clide/window method channel with a GdkWindowEdge direction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-8
@@ -84,14 +84,17 @@ class _RootShellState extends State<_RootShell> {
|
||||
onKeyEvent: _onKey,
|
||||
child: ColoredBox(
|
||||
color: tokens.globalBackground,
|
||||
child: DialogHost(
|
||||
router: widget.services.dialog,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: RootLayout()),
|
||||
const ClidePalette(),
|
||||
const Positioned.fill(child: _WelcomeOverlay()),
|
||||
],
|
||||
child: ClideResizeBorder(
|
||||
windowControls: widget.services.window,
|
||||
child: DialogHost(
|
||||
router: widget.services.dialog,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: RootLayout()),
|
||||
const ClidePalette(),
|
||||
const Positioned.fill(child: _WelcomeOverlay()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
|
||||
|
||||
enum ChromeStyle { seam, prompt, inline }
|
||||
|
||||
enum ResizeEdge { topLeft, top, topRight, left, right, bottomLeft, bottom, bottomRight }
|
||||
|
||||
class WindowControls extends ChangeNotifier {
|
||||
static const _channel = MethodChannel('clide/window');
|
||||
|
||||
@@ -15,6 +17,14 @@ class WindowControls extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> startResize(ResizeEdge edge) async {
|
||||
try {
|
||||
await _channel.invokeMethod('startResize', edge.index);
|
||||
} on MissingPluginException {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> startDrag() async {
|
||||
try {
|
||||
await _channel.invokeMethod('startDrag');
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:clide/kernel/src/window_controls.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ClideResizeBorder extends StatelessWidget {
|
||||
const ClideResizeBorder({super.key, required this.windowControls, required this.child});
|
||||
|
||||
final WindowControls windowControls;
|
||||
final Widget child;
|
||||
|
||||
static const double _edge = 6;
|
||||
static const double _corner = 12;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(child: child),
|
||||
// Corners
|
||||
_zone(Alignment.topLeft, _corner, _corner, SystemMouseCursors.resizeUpLeft, ResizeEdge.topLeft),
|
||||
_zone(Alignment.topRight, _corner, _corner, SystemMouseCursors.resizeUpRight, ResizeEdge.topRight),
|
||||
_zone(Alignment.bottomLeft, _corner, _corner, SystemMouseCursors.resizeDownLeft, ResizeEdge.bottomLeft),
|
||||
_zone(Alignment.bottomRight, _corner, _corner, SystemMouseCursors.resizeDownRight, ResizeEdge.bottomRight),
|
||||
// Edges
|
||||
Positioned(top: _corner, bottom: _corner, left: 0, width: _edge, child: _edgeZone(SystemMouseCursors.resizeLeft, ResizeEdge.left)),
|
||||
Positioned(top: _corner, bottom: _corner, right: 0, width: _edge, child: _edgeZone(SystemMouseCursors.resizeRight, ResizeEdge.right)),
|
||||
Positioned(left: _corner, right: _corner, top: 0, height: _edge, child: _edgeZone(SystemMouseCursors.resizeUp, ResizeEdge.top)),
|
||||
Positioned(left: _corner, right: _corner, bottom: 0, height: _edge, child: _edgeZone(SystemMouseCursors.resizeDown, ResizeEdge.bottom)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _zone(Alignment alignment, double w, double h, MouseCursor cursor, ResizeEdge edge) {
|
||||
return Positioned(
|
||||
left: alignment.x < 0 ? 0 : null,
|
||||
right: alignment.x > 0 ? 0 : null,
|
||||
top: alignment.y < 0 ? 0 : null,
|
||||
bottom: alignment.y > 0 ? 0 : null,
|
||||
width: w,
|
||||
height: h,
|
||||
child: _edgeZone(cursor, edge),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _edgeZone(MouseCursor cursor, ResizeEdge edge) {
|
||||
return MouseRegion(
|
||||
cursor: cursor,
|
||||
child: GestureDetector(
|
||||
onPanStart: (_) => windowControls.startResize(edge),
|
||||
child: const ColoredBox(color: Color(0x00000000)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export 'src/clide_icon.dart';
|
||||
export 'src/clide_icon_rail.dart';
|
||||
export 'src/clide_palette.dart';
|
||||
export 'src/clide_pane_chrome.dart';
|
||||
export 'src/clide_resize_border.dart';
|
||||
export 'src/clide_pty_view.dart';
|
||||
export 'src/clide_scrollbar.dart';
|
||||
export 'src/clide_spine.dart';
|
||||
|
||||
@@ -113,6 +113,26 @@ static void my_application_activate(GApplication* application) {
|
||||
gtk_window_close(w);
|
||||
response = FL_METHOD_RESPONSE(
|
||||
fl_method_success_response_new(fl_value_new_null()));
|
||||
} else if (g_strcmp0(method, "startResize") == 0) {
|
||||
// edge: 0=topLeft 1=top 2=topRight 3=left 4=right
|
||||
// 5=bottomLeft 6=bottom 7=bottomRight
|
||||
FlValue* args = fl_method_call_get_args(method_call);
|
||||
int edge = 7; // default: bottom-right
|
||||
if (fl_value_get_type(args) == FL_VALUE_TYPE_INT) {
|
||||
edge = (int)fl_value_get_int(args);
|
||||
}
|
||||
static const GdkWindowEdge edges[] = {
|
||||
GDK_WINDOW_EDGE_NORTH_WEST, GDK_WINDOW_EDGE_NORTH,
|
||||
GDK_WINDOW_EDGE_NORTH_EAST, GDK_WINDOW_EDGE_WEST,
|
||||
GDK_WINDOW_EDGE_EAST, GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
GDK_WINDOW_EDGE_SOUTH, GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
};
|
||||
if (edge >= 0 && edge < 8) {
|
||||
gtk_window_begin_resize_drag(w, edges[edge], 1, 0, 0,
|
||||
GDK_CURRENT_TIME);
|
||||
}
|
||||
response = FL_METHOD_RESPONSE(
|
||||
fl_method_success_response_new(fl_value_new_null()));
|
||||
} else if (g_strcmp0(method, "isMaximized") == 0) {
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(
|
||||
fl_value_new_bool(gtk_window_is_maximized(w))));
|
||||
|
||||
Reference in New Issue
Block a user