文章目录
- [ai + fluent_ui 实现自定义winUI风格窗口](#ai + fluent_ui 实现自定义winUI风格窗口)
-
- 完整代码与效果预览
- 目录
- [1. Fluent UI 是什么?](#1. Fluent UI 是什么?)
-
- [1.1 基本概念](#1.1 基本概念)
- [1.2 官方资源](#1.2 官方资源)
- [1.3 核心特性](#1.3 核心特性)
- [1.4 基本使用方式](#1.4 基本使用方式)
- [2. window_manager 是什么?](#2. window_manager 是什么?)
-
- [2.1 基本概念](#2.1 基本概念)
- [2.2 官方资源](#2.2 官方资源)
- [2.3 核心功能](#2.3 核心功能)
- [2.4 基本使用方式](#2.4 基本使用方式)
- [3. WinUI 是什么?](#3. WinUI 是什么?)
-
- [3.1 基本概念](#3.1 基本概念)
- [3.2 WinUI 的特点](#3.2 WinUI 的特点)
- [3.3 Fluent UI 与 WinUI 的关系](#3.3 Fluent UI 与 WinUI 的关系)
- [3.4 为什么选择 Fluent UI?](#3.4 为什么选择 Fluent UI?)
- [4. 跨平台支持](#4. 跨平台支持)
-
- [4.1 Fluent UI 的跨平台支持](#4.1 Fluent UI 的跨平台支持)
- [4.2 window_manager 的跨平台支持](#4.2 window_manager 的跨平台支持)
- [4.3 本项目的跨平台特性](#4.3 本项目的跨平台特性)
- [4.4 平台特定配置](#4.4 平台特定配置)
- 项目代码结构解析
-
- [5.1 项目文件说明](#5.1 项目文件说明)
- [5.2 核心代码解析](#5.2 核心代码解析)
- 总结
- 参考链接汇总
ai + fluent_ui 实现自定义winUI风格窗口
这是一个使用 Fluent UI 和 window_manager 插件实现的跨平台统一 Windows UI 风格的示例程序。本文档将对这些技术进行详细介绍,帮助初学者理解项目的核心概念。
完整代码与效果预览

一个示例面板

效果预览
本项目实现了以下效果:
| 效果 | 说明 |
|---|---|
| 自定义 WinUI 风格标题栏 | 隐藏系统原生标题栏,使用 Fluent UI 组件自定义标题栏 |
| 窗口控制按钮 | 包含最小化、最大化/还原、关闭按钮 |
| Windows 11 风格 UI | 使用 Fluent UI 组件构建的现代化界面 |
| 导航侧边栏 | 支持折叠/展开的侧边栏导航 |
| 流畅动画 | 窗口最大/最小化动画、导航切换动画 |
项目完整代码
1️⃣ 主入口文件 (lib/winui_title_bar.dart)
这是项目的核心入口文件,实现了自定义窗口标题栏和 Fluent UI 导航布局:
dart
import 'package:fluent_ui/fluent_ui.dart';
import 'package:window_manager/window_manager.dart';
void main() async {
// 1. 确保 Flutter 绑定已初始化
WidgetsFlutterBinding.ensureInitialized();
// 2. 初始化 windowManager
await windowManager.ensureInitialized();
// 3. 配置窗口选项:隐藏系统标题栏
WindowOptions windowOptions = WindowOptions(
titleBarStyle: TitleBarStyle.hidden,
);
// 4. 等待窗口准备好后显示
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.focus();
});
// 5. 启动应用
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return FluentApp(
debugShowCheckedModeBanner: false,
title: 'Fluent UI Demo',
// 浅色主题
theme: FluentThemeData(
brightness: Brightness.light,
accentColor: Colors.blue,
),
// 深色主题
darkTheme: FluentThemeData(
brightness: Brightness.dark,
accentColor: Colors.blue,
),
home: const NavigationViewDemo(),
);
}
}
class NavigationViewDemo extends StatefulWidget {
const NavigationViewDemo({super.key});
@override
State<NavigationViewDemo> createState() => _NavigationViewDemoState();
}
class _NavigationViewDemoState extends State<NavigationViewDemo> {
int _currentIndex = 0;
bool _isMaximized = false;
// 切换最大化/还原状态
void _toggleMaximize() {
if (_isMaximized) {
windowManager.unmaximize();
} else {
windowManager.maximize();
}
setState(() => _isMaximized = !_isMaximized);
}
@override
Widget build(BuildContext context) {
return NavigationView(
// 自定义应用栏(标题栏)
appBar: NavigationAppBar(
// 标题区域:使用 DragToMoveArea 允许拖动窗口
title: DragToMoveArea(
child: Container(
alignment: Alignment.centerLeft,
child: const Text('Fluent UI 导航演示'),
),
),
// 右侧窗口控制按钮
actions: Center(
child: Row(
spacing: 20.0,
verticalDirection: VerticalDirection.down,
mainAxisAlignment: MainAxisAlignment.end,
children: [
// 最小化按钮
IconButton(
icon: WindowsIcon(FluentIcons.chrome_minimize),
onPressed: windowManager.minimize,
),
// 最大化/还原按钮
IconButton(
icon: WindowsIcon(
_isMaximized
? FluentIcons.chrome_restore
: FluentIcons.grid_view_large,
),
onPressed: _toggleMaximize,
),
// 关闭按钮
IconButton(
icon: WindowsIcon(FluentIcons.chrome_close),
onPressed: windowManager.close,
),
SizedBox(), // 占位符
],
),
),
),
// 导航面板
pane: NavigationPane(
selected: _currentIndex,
onChanged: (index) => setState(() => _currentIndex = index),
displayMode: PaneDisplayMode.auto, // 自动适应宽度
items: [
// 首页导航项
PaneItem(
icon: const Icon(FluentIcons.home),
title: const Text('首页'),
body: const Center(child: Text('欢迎来到首页')),
),
// 设置导航项
PaneItem(
icon: const Icon(FluentIcons.settings),
title: const Text('设置'),
body: const Center(child: Text('这里是设置页面')),
),
],
),
);
}
}
2️⃣ 组件演示文件 (lib/main.dart)
这个文件展示了 Fluent UI 的各种基础组件和高级组件的用法:
dart
import 'package:fluent_ui/fluent_ui.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// 状态变量
bool checked = false;
bool checkboxValue = false;
double sliderValue = 50.0;
bool disabled = false;
AccentColor splitButtonColor = Colors.blue;
// 全局 Key 用于访问组件状态
final key = GlobalKey<MenuBarState>();
final splitButtonKey = GlobalKey<SplitButtonState>();
final expanderKey = GlobalKey<ExpanderState>(debugLabel: 'Expander key');
@override
Widget build(BuildContext context) {
return FluentApp(
title: 'Windows UI Layout',
theme: FluentThemeData(
brightness: Brightness.light,
accentColor: Colors.blue,
),
home: ScaffoldPage(
header: const PageHeader(title: Text('分层布局示例')),
content: SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 菜单栏
MenuBar(
key: key,
items: [
MenuBarItem(
title: 'File',
items: [
MenuFlyoutItem(text: const Text('New'), onPressed: () {}),
MenuFlyoutItem(text: const Text('Open'), onPressed: () {}),
MenuFlyoutItem(text: const Text('Save'), onPressed: () {}),
MenuFlyoutItem(text: const Text('Exit'), onPressed: () {}),
],
),
MenuBarItem(
title: 'Edit',
items: [
MenuFlyoutItem(text: const Text('Cut'), onPressed: () {}),
MenuFlyoutItem(text: const Text('Copy'), onPressed: () {}),
MenuFlyoutItem(text: const Text('Paste'), onPressed: () {}),
],
),
MenuBarItem(
title: 'Help',
items: [
MenuFlyoutItem(
text: const Text('About'),
onPressed: () {},
),
],
),
],
),
const SizedBox(height: 10),
// ===== 第一部分:基础控制组件 =====
const Text('基础控制', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
Wrap(
spacing: 15.0,
runSpacing: 15.0,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
// 标准按钮
Button(child: const Text('标准按钮'), onPressed: () {}),
// 填充按钮
FilledButton(child: const Text('填充按钮'), onPressed: () {}),
// 切换开关
ToggleButton(
checked: checked,
onChanged: (v) => setState(() => checked = v),
child: const Text('切换开关'),
),
// 复选框
Checkbox(
checked: checkboxValue,
onChanged: (v) => setState(() => checkboxValue = v ?? false),
content: const Text('复选框'),
),
// 图标按钮
IconButton(
icon: const Icon(FluentIcons.disable_updates, size: 16.0),
onPressed: () => debugPrint('pressed'),
),
// 下拉菜单按钮
DropDownButton(
title: const Text('编程语言'),
items: [
MenuFlyoutItem(text: const Text('Python'), onPressed: () {}),
const MenuFlyoutSeparator(),
MenuFlyoutItem(text: const Text('C++'), onPressed: null),
MenuFlyoutItem(text: const Text('C#'), onPressed: () {}),
],
),
// 拆分按钮
SplitButton(
key: splitButtonKey,
enabled: !disabled,
child: Container(
height: 32,
width: 36,
decoration: BoxDecoration(
color: splitButtonColor,
borderRadius: const BorderRadiusDirectional.horizontal(
start: Radius.circular(4.0),
),
),
),
flyout: FlyoutContent(
constraints: const BoxConstraints(maxWidth: 200.0),
child: Wrap(
runSpacing: 10.0,
spacing: 8.0,
children: Colors.accentColors.map((color) {
return Button(
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.all(4.0)),
),
onPressed: () {
setState(() => splitButtonColor = color);
Navigator.of(context).pop();
},
child: Container(
height: 32,
width: 32,
color: color,
),
);
}).toList(),
),
),
),
],
),
const SizedBox(height: 30),
const Divider(),
const SizedBox(height: 30),
// ===== 第二部分:高级组件 =====
const Text('高级组件', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
// 滑块
Slider(
label: '${sliderValue.toInt()}%',
value: sliderValue,
onChanged: (v) => setState(() => sliderValue = v),
),
const SizedBox(height: 20),
// 信息提示条
const InfoBar(
title: Text('系统提示'),
content: Text('这是强制换行后放置在底部的长内容区域。'),
severity: InfoBarSeverity.info,
),
const SizedBox(height: 20),
// 开关
ToggleSwitch(
checked: checked,
onChanged: disabled ? null : (v) => setState(() => checked = v),
),
// 可展开面板
Expander(
leading: RadioButton(
checked: checked,
onChanged: (v) => setState(() => checked = v),
),
header: Text('This text is in header'),
content: Text('This text is in content'),
),
Expander(
header: Text('Open to see the scrollable text'),
content: SizedBox(
height: 300,
child: SingleChildScrollView(
child: Text('A LONG TEXT HERE' * 20),
),
),
),
// 日历视图
CalendarView(
selectionMode: CalendarViewSelectionMode.single,
onSelectionChanged: (value) {
debugPrint('${value.selectedDates}');
},
isOutOfScopeEnabled: false,
isGroupLabelVisible: false,
locale: const Locale('zh'),
),
],
),
),
),
);
}
}
3️⃣ 项目依赖配置 (pubspec.yaml)
yaml
name: flent_ui_app
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.10.4
dependencies:
flutter:
sdk: flutter
fluent_ui: ^4.13.0 # Fluent UI 组件库
window_manager: ^0.5.1 # 窗口管理插件
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
如何运行项目
bash
# 1. 进入项目目录
cd flent_ui_app
# 2. 获取依赖
flutter pub get
# 3. 运行项目(Windows 平台)
flutter run -d windows
目录
- [什么是 Fluent UI?](#什么是 Fluent UI?)
- [什么是 window_manager?](#什么是 window_manager?)
- [什么是 WinUI?](#什么是 WinUI?)
- 跨平台支持
- 项目代码结构解析
1. Fluent UI 是什么?
1.1 基本概念
Fluent UI 是微软官方设计系统的 Flutter 实现版本。它提供了一套遵循微软 Fluent Design System 设计规范的 UI 组件库,让 Flutter 应用能够拥有与 Windows 11 原生应用一致的视觉效果和交互体验。
简单来说:如果你想让你的 Flutter 应用看起来像 Windows 原生应用,使用 Fluent UI 就对了!
1.2 官方资源
1.3 核心特性
Fluent UI 提供了丰富的 Windows 风格组件,包括但不限于:
| 组件类别 | 示例组件 | 说明 |
|---|---|---|
| 按钮类 | Button、FilledButton、ToggleButton |
标准按钮、填充按钮、开关按钮 |
| 输入类 | Checkbox、TextBox、RadioButton |
复选框、文本框、单选按钮 |
| 导航类 | NavigationView、Pane、TabView |
导航视图、侧边栏、标签页 |
| 信息展示 | InfoBar、Tooltip、ProgressBar |
信息提示条、进度条 |
| 布局类 | Expander、SplitButton、Flyout |
可展开面板、下拉菜单 |
| 日期时间 | CalendarView、TimePicker |
日历视图、时间选择器 |
1.4 基本使用方式
安装依赖:
yaml
dependencies:
fluent_ui: ^4.13.0
基本使用示例:
dart
import 'package:fluent_ui/fluent_ui.dart';
// 使用 FluentApp 作为根组件
FluentApp(
title: '我的应用',
theme: FluentThemeData(
brightness: Brightness.light, // 浅色主题
accentColor: Colors.blue, // 主题色
),
home: HomePage(),
)
使用 Fluent UI 组件:
dart
// 按钮组件
Button(
child: Text('点击我'),
onPressed: () => print('点击事件'),
)
// 复选框
Checkbox(
checked: isChecked,
onChanged: (value) => setState(() => isChecked = value),
)
// 导航视图
NavigationView(
pane: NavigationPane(
items: [
PaneItem(icon: Icon(FluentIcons.home), title: Text('首页')),
PaneItem(icon: Icon(FluentIcons.settings), title: Text('设置')),
],
),
)
2. window_manager 是什么?
2.1 基本概念
window_manager 是一个 Flutter 插件,专门用于控制桌面应用程序窗口的行为和外观。它允许开发者自定义窗口标题栏、控制窗口大小、位置、最大/最小化等操作,实现更灵活的桌面应用体验。
简单来说:如果你想让你的 Flutter 桌面应用有自定义的标题栏、可以自由拖动、缩放窗口,window_manager 就是你需要的工具!
2.2 官方资源
| 资源类型 | 链接 |
|---|---|
| Pub.dev 包地址 | https://pub.dev/packages/window_manager |
| GitHub 仓库 | https://github.com/leanflutter/window_manager |
2.3 核心功能
| 功能 | 说明 | 代码示例 |
|---|---|---|
| 隐藏标题栏 | 使用自定义标题栏替换系统默认 | TitleBarStyle.hidden |
| 窗口控制 | 最小化、最大化、关闭窗口 | windowManager.minimize() |
| 窗口状态 | 判断是否最大化 | windowManager.isMaximized() |
| 窗口大小 | 获取或设置窗口尺寸 | windowManager.setSize(Size(800, 600)) |
| 窗口位置 | 获取或设置窗口位置 | windowManager.setPosition(Offset(100, 100)) |
| 置顶显示 | 保持窗口在其他应用之上 | windowManager.setAlwaysOnTop(true) |
| 窗口事件 | 监听窗口状态变化 | windowManager.addListener(...) |
2.4 基本使用方式
安装依赖:
yaml
dependencies:
window_manager: ^0.5.1
初始化配置:
dart
import 'package:window_manager/window_manager.dart';
void main() async {
// 确保 Flutter 绑定已初始化
WidgetsFlutterBinding.ensureInitialized();
// 初始化 windowManager
await windowManager.ensureInitialized();
// 配置窗口选项
WindowOptions windowOptions = WindowOptions(
titleBarStyle: TitleBarStyle.hidden, // 隐藏系统标题栏
backgroundColor: Colors.transparent, // 背景透明
);
// 等待窗口准备好后显示
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.focus();
});
runApp(const MyApp());
}
在应用中使用窗口控制:
dart
// 创建自定义标题栏
NavigationAppBar(
title: DragToMoveArea( // 允许拖动窗口
child: Container(
alignment: Alignment.centerLeft,
child: Text('自定义标题栏'),
),
),
actions: Row(
children: [
// 最小化按钮
IconButton(
icon: Icon(FluentIcons.chrome_minimize),
onPressed: windowManager.minimize,
),
// 最大化/还原按钮
IconButton(
icon: Icon(FluentIcons.grid_view_large),
onPressed: () async {
if (await windowManager.isMaximized()) {
windowManager.unmaximize();
} else {
windowManager.maximize();
}
},
),
// 关闭按钮
IconButton(
icon: Icon(FluentIcons.chrome_close),
onPressed: windowManager.close,
),
],
),
)
3. WinUI 是什么?
3.1 基本概念
WinUI 是 Windows UI Library 的缩写,是微软官方的原生 Windows 应用界面库。它是 Fluent Design System 的核心实现,用于构建现代化、美观的 Windows 桌面应用。
3.2 WinUI 的特点
| 特性 | 说明 |
|---|---|
| 原生体验 | 拥有最正宗的 Windows 11 视觉风格和交互效果 |
| 高性能 | 直接使用 Windows 原生 API,无需额外抽象层 |
| 现代设计 | 支持圆角、亚克力/云母材质效果、毛玻璃动画 |
| 持续更新 | 跟随 Windows 系统更新,不断添加新特性 |
| 官方支持 | 微软官方维护,有完善的技术支持 |
3.3 Fluent UI 与 WinUI 的关系
┌─────────────────────────────────────────────────────────┐
│ 你的 Flutter 应用 │
├─────────────────────────────────────────────────────────┤
│ Fluent UI (Flutter) │
│ 提供与 WinUI 一致的组件和行为表现 │
├─────────────────────────────────────────────────────────┤
│ window_manager (Flutter 插件) │
│ 控制窗口行为:拖动、最大/最小化等 │
├─────────────────────────────────────────────────────────┤
│ Windows 原生平台 (WinUI) │
│ Fluent Design System 官方实现 │
└─────────────────────────────────────────────────────────┘
3.4 为什么选择 Fluent UI?
| 优势 | 说明 |
|---|---|
| 跨平台 | 一套代码可在 Windows、macOS、Linux、Web 上运行 |
| Flutter 生态 | 可使用 Flutter 丰富的插件和工具 |
| 原生外观 | 视觉效果与原生 WinUI 应用非常接近 |
| 开发效率 | 热重载、声明式 UI 等 Flutter 优势 |
4. 跨平台支持
4.1 Fluent UI 的跨平台支持
Fluent UI 主要是 Windows 风格 的 UI 库,但它在设计上考虑了多平台支持:
| 平台 | 支持程度 | 说明 |
|---|---|---|
| Windows | ✅ 完全支持 | 官方主要支持平台,效果最佳 |
| macOS | ✅ 支持 | 有一定的适配,部分组件可用 |
| Linux | ✅ 支持 | 有一定的适配,部分组件可用 |
| Web | ⚠️ 部分支持 | 实验性支持,功能受限 |
| iOS/Android | ❌ 不推荐 | 风格不匹配,不建议使用 |
4.2 window_manager 的跨平台支持
| 平台 | 支持程度 | 说明 |
|---|---|---|
| Windows | ✅ 完全支持 | 所有功能可用 |
| macOS | ✅ 完全支持 | 所有功能可用 |
| Linux | ✅ 完全支持 | 所有功能可用 |
| Web | ❌ 不支持 | 浏览器环境限制 |
| iOS/Android | ❌ 不支持 | 移动端不适用 |
4.3 本项目的跨平台特性
本项目通过以下组合实现跨平台开发:
┌──────────────────────────────────────────────────────────┐
│ 共享业务逻辑层 │
│ (Dart 语言,一套代码多端运行) │
├──────────────────┬─────────────────┬─────────────────────┤
│ Windows 平台 │ macOS 平台 │ Linux 平台 │
│ fluent_ui + │ fluent_ui + │ fluent_ui + │
│ window_manager │ window_manager │ window_manager │
├──────────────────┴─────────────────┴─────────────────────┤
│ 统一的 Windows 11 风格界面 │
└──────────────────────────────────────────────────────────┘
4.4 平台特定配置
Windows 平台配置 (本项目示例):
dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Windows 特有:隐藏标题栏使用自定义标题栏
await windowManager.ensureInitialized();
WindowOptions windowOptions = WindowOptions(
titleBarStyle: TitleBarStyle.hidden,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.focus();
});
runApp(const MyApp());
}
多平台条件编译示例:
dart
import 'package:flutter/foundation.dart' show kIsWeb;
// 根据不同平台显示不同 UI
Widget buildAdaptiveLayout() {
if (kIsWeb) {
// Web 平台特殊处理
return WebLayout();
} else {
// 桌面平台使用 Fluent UI
return NavigationView(
pane: NavigationPane(items: [...]),
);
}
}
项目代码结构解析
5.1 项目文件说明
| 文件路径 | 功能说明 |
|---|---|
lib/main.dart |
应用入口,包含基础 Fluent UI 组件演示 |
lib/winui_title_bar.dart |
自定义 WinUI 风格标题栏实现 |
pubspec.yaml |
项目依赖配置 |
5.2 核心代码解析
入口文件 (winui_title_bar.dart):
dart
void main() async {
// 1. 初始化 Flutter 绑定
WidgetsFlutterBinding.ensureInitialized();
// 2. 初始化窗口管理器
await windowManager.ensureInitialized();
// 3. 配置窗口选项(隐藏系统标题栏)
WindowOptions windowOptions = WindowOptions(
titleBarStyle: TitleBarStyle.hidden,
);
// 4. 等待窗口准备好后显示
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.focus();
});
// 5. 启动应用
runApp(const MyApp());
}
自定义标题栏实现:
dart
NavigationView(
appBar: NavigationAppBar(
// 使用 DragToMoveArea 允许拖动窗口
title: DragToMoveArea(
child: Container(
alignment: Alignment.centerLeft,
child: const Text('Fluent UI 导航演示'),
),
),
// 自定义窗口控制按钮
actions: Row(
children: [
IconButton(
icon: Icon(FluentIcons.chrome_minimize),
onPressed: windowManager.minimize,
),
IconButton(
icon: Icon(FluentIcons.grid_view_large),
onPressed: _toggleMaximize,
),
IconButton(
icon: Icon(FluentIcons.chrome_close),
onPressed: windowManager.close,
),
],
),
),
// 导航面板
pane: NavigationPane(
items: [
PaneItem(icon: Icon(FluentIcons.home), title: Text('首页')),
PaneItem(icon: Icon(FluentIcons.settings), title: Text('设置')),
],
),
)
总结
本项目展示了如何使用 Fluent UI 和 window_manager 构建具有 Windows 11 风格的跨平台桌面应用:
- Fluent UI 提供了丰富的 Windows 风格组件,让应用拥有原生外观
- window_manager 提供了窗口控制能力,支持自定义标题栏
- 结合两者,可以在 Windows、macOS、Linux 上实现统一的 UI 体验
这种开发方式既保留了 Flutter 的跨平台优势,又实现了原生 Windows 应用的视觉效果,是一个非常实用的技术组合!
参考链接汇总
| 资源 | 链接 |
|---|---|
| Fluent UI Pub.dev | https://pub.dev/packages/fluent_ui |
| Fluent UI GitHub | https://github.com/bdlukaa/fluent_ui |
| window_manager Pub.dev | https://pub.dev/packages/window_manager |
| window_manager GitHub | https://github.com/leanflutter/window_manager |
| Fluent Design System | https://fluent.microsoft.com/ |
| Flutter 官网 | https://flutter.dev/ |