make hat bar full-width, separators 1px visible
Hat bar is now a single continuous row above the three columns instead of per-column segments. Left: macOS traffic lights or empty. Center: project > branch. Right: minimize/maximize/close on Linux or empty. Column separators keep 4px hit area but show a 1px centered line. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+144
-16
@@ -1,7 +1,10 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:clide/builtin/welcome/src/welcome_view.dart';
|
import 'package:clide/builtin/welcome/src/welcome_view.dart';
|
||||||
import 'package:clide/extension/src/contribution.dart';
|
import 'package:clide/extension/src/contribution.dart';
|
||||||
import 'package:clide/kernel/kernel.dart';
|
import 'package:clide/kernel/kernel.dart';
|
||||||
import 'package:clide/widgets/widgets.dart';
|
import 'package:clide/widgets/widgets.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
@@ -129,6 +132,7 @@ class RootLayout extends StatelessWidget {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
|
_HatBar(kernel: kernel),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -141,10 +145,7 @@ class RootLayout extends StatelessWidget {
|
|||||||
else if (sidebarVisible) ...[
|
else if (sidebarVisible) ...[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: sidebarSize,
|
width: sidebarSize,
|
||||||
child: Column(children: [
|
child: SlotHost(slot: Slots.sidebar),
|
||||||
ColumnHat.left(windowControls: kernel.window),
|
|
||||||
Expanded(child: SlotHost(slot: Slots.sidebar)),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
DragResizeHandle(
|
DragResizeHandle(
|
||||||
arrangement: a,
|
arrangement: a,
|
||||||
@@ -152,14 +153,7 @@ class RootLayout extends StatelessWidget {
|
|||||||
axis: Axis.horizontal,
|
axis: Axis.horizontal,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Expanded(child: Column(children: [
|
const Expanded(child: SlotHost(slot: Slots.workspace)),
|
||||||
ColumnHat.center(
|
|
||||||
windowControls: kernel.window,
|
|
||||||
project: kernel.project.current?.path.split('/').last,
|
|
||||||
branch: null,
|
|
||||||
),
|
|
||||||
const Expanded(child: SlotHost(slot: Slots.workspace)),
|
|
||||||
])),
|
|
||||||
if (contextVisible && contextCollapsed)
|
if (contextVisible && contextCollapsed)
|
||||||
ClideSpine(
|
ClideSpine(
|
||||||
label: 'context',
|
label: 'context',
|
||||||
@@ -174,10 +168,7 @@ class RootLayout extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: contextSize,
|
width: contextSize,
|
||||||
child: Column(children: [
|
child: SlotHost(slot: Slots.contextPanel),
|
||||||
ColumnHat.right(windowControls: kernel.window),
|
|
||||||
Expanded(child: SlotHost(slot: Slots.contextPanel)),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -219,6 +210,143 @@ class RootLayout extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _HatBar extends StatelessWidget {
|
||||||
|
const _HatBar({required this.kernel});
|
||||||
|
final KernelServices kernel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final tokens = ClideTheme.of(context).surface;
|
||||||
|
return GestureDetector(
|
||||||
|
onPanStart: (_) => kernel.window.startDrag(),
|
||||||
|
child: Container(
|
||||||
|
height: hatHeight,
|
||||||
|
color: tokens.panelHeader,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_LeftHatContent(tokens: tokens, wc: kernel.window),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: ListenableBuilder(
|
||||||
|
listenable: kernel.project,
|
||||||
|
builder: (ctx, _) {
|
||||||
|
final name = kernel.project.current?.path.split('/').last;
|
||||||
|
return ClideText(
|
||||||
|
name != null ? 'clide > $name' : 'clide',
|
||||||
|
fontSize: 12,
|
||||||
|
color: tokens.globalTextMuted,
|
||||||
|
fontFamily: clideMonoFamily,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_RightHatContent(tokens: tokens, wc: kernel.window),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeftHatContent extends StatelessWidget {
|
||||||
|
const _LeftHatContent({required this.tokens, required this.wc});
|
||||||
|
final SurfaceTokens tokens;
|
||||||
|
final WindowControls wc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (kIsWeb) return const SizedBox.shrink();
|
||||||
|
if (!kIsWeb && Platform.isMacOS) {
|
||||||
|
return Row(children: [
|
||||||
|
_TrafficDot(color: const Color(0xFFFF5F57), onTap: wc.close),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_TrafficDot(color: const Color(0xFFFEBC2E), onTap: wc.minimize),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_TrafficDot(color: const Color(0xFF28C840), onTap: wc.toggleMaximize),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RightHatContent extends StatelessWidget {
|
||||||
|
const _RightHatContent({required this.tokens, required this.wc});
|
||||||
|
final SurfaceTokens tokens;
|
||||||
|
final WindowControls wc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (kIsWeb) return const SizedBox.shrink();
|
||||||
|
if (!kIsWeb && Platform.isMacOS) return const SizedBox.shrink();
|
||||||
|
return Row(children: [
|
||||||
|
_WinBtn(icon: const PhosphorIconPainter(0xe32a), onTap: wc.minimize, tokens: tokens),
|
||||||
|
_WinBtn(icon: const PhosphorIconPainter(0xe45e), onTap: wc.toggleMaximize, tokens: tokens),
|
||||||
|
_WinBtn(icon: PhosphorIcons.xMark, onTap: wc.close, tokens: tokens, isClose: true),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TrafficDot extends StatefulWidget {
|
||||||
|
const _TrafficDot({required this.color, required this.onTap});
|
||||||
|
final Color color;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
@override
|
||||||
|
State<_TrafficDot> createState() => _TrafficDotState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TrafficDotState extends State<_TrafficDot> {
|
||||||
|
bool _hover = false;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _hover = true),
|
||||||
|
onExit: (_) => setState(() => _hover = false),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: widget.onTap,
|
||||||
|
child: Container(
|
||||||
|
width: 12, height: 12,
|
||||||
|
decoration: BoxDecoration(color: _hover ? widget.color : widget.color.withAlpha(0xCC), shape: BoxShape.circle),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WinBtn extends StatefulWidget {
|
||||||
|
const _WinBtn({required this.icon, required this.onTap, required this.tokens, this.isClose = false});
|
||||||
|
final ClideIconPainter icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final SurfaceTokens tokens;
|
||||||
|
final bool isClose;
|
||||||
|
@override
|
||||||
|
State<_WinBtn> createState() => _WinBtnState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WinBtnState extends State<_WinBtn> {
|
||||||
|
bool _hover = false;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final hoverBg = widget.isClose ? const Color(0xFFE81123) : widget.tokens.listItemHoverBackground;
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _hover = true),
|
||||||
|
onExit: (_) => setState(() => _hover = false),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: widget.onTap,
|
||||||
|
child: Container(
|
||||||
|
width: 36, height: hatHeight,
|
||||||
|
color: _hover ? hoverBg : null,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ClideIcon(widget.icon, size: 14, color: _hover && widget.isClose ? const Color(0xFFFFFFFF) : widget.tokens.globalTextMuted),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SlotHost extends StatelessWidget {
|
class SlotHost extends StatelessWidget {
|
||||||
const SlotHost({super.key, required this.slot});
|
const SlotHost({super.key, required this.slot});
|
||||||
final SlotId slot;
|
final SlotId slot;
|
||||||
|
|||||||
@@ -44,10 +44,16 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
|
|||||||
onPointerDown: _onDown,
|
onPointerDown: _onDown,
|
||||||
onPointerMove: _onMove,
|
onPointerMove: _onMove,
|
||||||
onPointerUp: _onUp,
|
onPointerUp: _onUp,
|
||||||
child: Container(
|
child: SizedBox(
|
||||||
width: widget.axis == Axis.horizontal ? widget.thickness : null,
|
width: widget.axis == Axis.horizontal ? widget.thickness : null,
|
||||||
height: widget.axis == Axis.vertical ? widget.thickness : null,
|
height: widget.axis == Axis.vertical ? widget.thickness : null,
|
||||||
color: color,
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: widget.axis == Axis.horizontal ? 1 : null,
|
||||||
|
height: widget.axis == Axis.vertical ? 1 : null,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ static void my_application_activate(GApplication* application) {
|
|||||||
GtkWindow* window =
|
GtkWindow* window =
|
||||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
// D-057: frameless custom chrome. The Flutter app draws its own
|
// D-057: frameless custom chrome — the Flutter app draws its own
|
||||||
// per-column hats with drag regions and window buttons.
|
// hat bar with drag regions and window buttons.
|
||||||
gtk_window_set_decorated(window, FALSE);
|
gtk_window_set_decorated(window, FALSE);
|
||||||
gtk_window_set_title(window, "clide");
|
gtk_window_set_title(window, "clide");
|
||||||
|
|
||||||
|
|
||||||
gtk_window_set_default_size(window, 1280, 720);
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
|
||||||
// Window icon — load from the bundled asset.
|
// Window icon — load from the bundled asset.
|
||||||
|
|||||||
Reference in New Issue
Block a user