draggable_overlay_window 1.0.0
draggable_overlay_window: ^1.0.0 copied to clipboard
A highly customizable draggable and resizable overlay window widget for Flutter. Perfect for creating floating panels, tool windows, and multi-window interfaces.
import 'package:draggable_overlay_window/draggable_overlay_window.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Overlay Window Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
// Basta envolver com WindowManagerScope!
return WindowManagerScope(
// Callback global quando qualquer janela é fechada
onWindowClosed: (id) {
debugPrint('Janela fechada: $id');
},
// Callback global quando qualquer janela ganha foco
onWindowFocused: (id) {
debugPrint('Janela em foco: $id');
},
child: Scaffold(
backgroundColor: Colors.grey[100],
body: ListView(
padding: const EdgeInsets.all(20),
children: [
const SizedBox(height: 50),
Text(
'Teste de Janelas Flutuantes',
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Clique em uma janela para trazê-la ao topo!',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
// Botões para abrir janelas
const WindowButtons(),
],
),
),
);
}
}
/// Widget com os botões para abrir janelas
class WindowButtons extends StatelessWidget {
const WindowButtons({super.key});
@override
Widget build(BuildContext context) {
// Obtém o controller do WindowManagerScope
final windows = WindowManagerScope.of(context);
return Wrap(
spacing: 10,
runSpacing: 10,
alignment: WrapAlignment.center,
children: [
// Janela padrão - permite múltiplas
ElevatedButton.icon(
icon: const Icon(Icons.window),
label: const Text('Padrão (Multi)'),
onPressed: () {
windows.open(
id: 'simple',
unique: false, // Permite múltiplas!
title: 'Simples',
content: const Center(
child: Text(
'Janela Padrão\nRedimensionável e Arrastável',
textAlign: TextAlign.center,
),
),
);
},
),
// Perfil - única
ElevatedButton.icon(
icon: const Icon(Icons.person),
label: const Text('Perfil (Único)'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade100,
),
onPressed: () {
windows.open(
id: 'profile',
title: 'Perfil do Usuário',
content: const UserProfileContent(),
initialSize: const Size(350, 500),
config: DraggableWindowConfig(
headerBackgroundColor: Colors.blue.shade700,
headerIconColor: Colors.white,
enableScrolling: false,
headerTextStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
);
},
),
// Nota - permite múltiplas
ElevatedButton.icon(
icon: const Icon(Icons.sticky_note_2),
label: const Text('Nota (Multi)'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.yellow.shade100,
),
onPressed: () {
windows.open(
id: 'note',
unique: false,
title: 'Lembrete',
initialSize: const Size(200, 200),
content: Container(
color: Colors.yellow.shade50,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Nota Rápida',
style: TextStyle(
fontFamily: 'cursive',
fontSize: 24,
color: Colors.grey.shade800,
),
),
const Divider(),
const Text('Escreva algo...'),
],
),
),
config: DraggableWindowConfig(
windowBackgroundColor: Colors.yellow.shade50,
headerBackgroundColor: Colors.yellow.shade700,
borderColor: Colors.yellow.shade900,
dividerColor: Colors.yellow.shade900,
borderRadius: 0,
elevation: 4,
),
);
},
),
// Aviso - única
ElevatedButton.icon(
icon: const Icon(Icons.lock),
label: const Text('Aviso (Único)'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade100,
),
onPressed: () {
windows.open(
id: 'alert',
title: 'Aviso Importante',
initialSize: const Size(300, 200),
content: const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.warning, size: 40, color: Colors.orange),
SizedBox(height: 10),
Text(
'Esta é uma janela de alerta fixa.\nNão pode ser redimensionada.',
textAlign: TextAlign.center,
),
],
),
),
),
config: const DraggableWindowConfig(
resizable: false,
borderColor: Colors.red,
headerBackgroundColor: Colors.red,
headerIconColor: Colors.white,
headerTextStyle: TextStyle(color: Colors.white),
),
);
},
),
// Player - única
ElevatedButton.icon(
icon: const Icon(Icons.music_note),
label: const Text('Player (Único)'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey.shade300,
),
onPressed: () {
windows.open(
id: 'player',
title: 'Now Playing',
initialSize: const Size(400, 225),
content: Container(
color: Colors.black,
child: Center(
child: Icon(
Icons.play_circle_fill,
size: 64,
color: Colors.white.withValues(alpha: 0.8),
),
),
),
config: const DraggableWindowConfig(
windowBackgroundColor: Colors.black,
headerBackgroundColor: Colors.black,
headerIconColor: Colors.white,
headerTextStyle: TextStyle(color: Colors.white),
borderColor: Colors.grey,
dividerColor: Colors.grey,
),
);
},
),
// Calculadora - única, com tamanho responsivo
ElevatedButton.icon(
icon: const Icon(Icons.code),
label: const Text('Calc (Único)'),
onPressed: () {
windows.open(
id: 'calculator',
title: 'Meia Tela',
initialPosition: const Offset(10, 300),
content: const Center(child: Text('50% da largura da tela')),
config: DraggableWindowConfig(
widthCalculator: (screenWidth) => screenWidth * 0.5,
heightCalculator: (screenHeight) => 200,
),
);
},
),
const SizedBox(width: double.infinity, height: 20),
// Botão para fechar todas
OutlinedButton.icon(
icon: const Icon(Icons.close_fullscreen),
label: const Text('Fechar Todas'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
),
onPressed: () {
windows.closeAll();
},
),
],
);
}
}
class UserProfileContent extends StatelessWidget {
const UserProfileContent({super.key});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
const CircleAvatar(
radius: 40,
child: Icon(Icons.person, size: 40),
),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
labelText: 'Nome',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.white,
),
controller: TextEditingController(text: 'Usuário Exemplo'),
),
const SizedBox(height: 12),
TextField(
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.white,
),
controller: TextEditingController(text: '[email protected]'),
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('Notificações'),
value: true,
onChanged: (val) {},
),
SwitchListTile(
title: const Text('Modo Escuro'),
value: false,
onChanged: (val) {},
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
child: const Text('Salvar'),
),
const SizedBox(height: 200),
const Center(child: Text("Final da lista")),
],
);
}
}