window_states 0.1.2
window_states: ^0.1.2 copied to clipboard
Effortless Window State Transitions for Flutter Desktop
Effortless Window State Transitions for Flutter Desktop #
A declarative Flutter package that brings smooth, animated window transitions to desktop applications. Transform your desktop app with animated window resizing and repositioning - perfect for mini players, sidebars, focus modes, and adaptive layouts.
🎯 The Problem #
Building desktop apps with multiple window layouts is surprisingly painful:
// ❌ The old way - Manual window management
await windowManager.setSize(Size(400, 600));
await windowManager.setPosition(Offset(100, 100));
await windowManager.setResizable(false);
// Now manually animate...
// Now switch your UI...
// Now keep everything in sync...
// 😫 100+ lines of boilerplate per transition
Common Pain Points:
- 🔧 Complex Coordination - Manually sync window size, position, and UI changes
- 🎬 Animation Hell - Writing custom animations for every window transition
- 🐛 State Management Chaos - Tracking which view is active and preventing race conditions
- 📱 Positioning Nightmares - Calculating pixel-perfect positions for every screen size
- 🔄 Boilerplate Explosion - Hundreds of lines for simple multi-layout apps
- ⚠️ Error-Prone - One wrong calculation and your window is off-screen
✨ The Solution #
Treat window configurations as navigable states with built-in animations:
// ✅ The new way - Declarative & animated
TransitionManager(
controller: controller,
views: [
ViewEntry(
view: CompactView(),
config: ViewConfig(
size: Size(400, 100),
position: WindowPosition.bottomRight,
padding: EdgeInsets.all(20),
shadow: WindowShadow.platformDefault,
animationConfig: AnimationConfig.fast(),
),
),
ViewEntry(
view: ExpandedView(),
config: ViewConfig(
size: Size(0.8, 0.9), // 80% width, 90% height
position: WindowPosition.center,
shadow: WindowShadow.none,
),
),
// No need to explicitly initialize the windowManager package now
// WindowInitializerConfig automatically picks up the default Size and configs from the `TransitionManager's initialViewIndex` and applies the config.
windowInitializerConfig: WindowInitializerConfig(),
// You can also explicitly configure the platforms from the `DesktopPlatform` enum, which applies the TransitionManager configs
// to only the mentioned platforms.
enabledPlatforms: DesktopPlatform.unixLike,
],
);
// Navigate with one line
TransitionManager.navigateToIndex(context, 1);
// 🎉 Automatic smooth animation, window resizing, and UI transition!
Examples: #
Pomodoro Timer - A simple Pomodoro Timer with Multiple Window Dimensions with a way to navigate in between them. #
🚀 Key Features #
-
Declarative Window Configurations Define all your window states upfront - no imperative spaghetti code.
-
Smooth Built-in Animations Professionally choreographed transitions between any window size/position.
-
Type-Safe Positioning System
WindowPosition.center
- Perfect centeringWindowPosition.topLeft
,topRight
,bottomLeft
,bottomRight
Corner positioningWindowPosition.centerLeft
,centerRight
- Edge-centered (perfect for sidebars)WindowPosition.custom
- Full control with custom calculators
-
Smart Sizing Support for both absolute and relative sizes:
size: Size(400, 300), // Absolute pixels size: Size(0.5, 0.8), // 50% width, 80% height
-
Shadow Control Fine-grained window shadow management:
shadow: WindowShadow.none, // No shadow shadow: WindowShadow.platformDefault, // OS default shadow shadow: CustomShadow( // Custom configuration color: Colors.black26, blurRadius: 15.0, offset: Offset(0, 8), )
-
Flexible Animations
Choose from presets or customize:
AnimationConfig.fast() // 250ms, snappy AnimationConfig.defaultConfig // 400ms, balanced AnimationConfig.slow() // 600ms, elegant AnimationConfig( duration: Duration(milliseconds: 300), curve: Curves.easeInOutCubic, steps: 20, )
-
Multiple Navigation Methods
// By index TransitionManager.navigateToIndex(context, 2); // By widget type (type-safe!) TransitionManager.navigateTo(context, DetailView());
📚 API Reference #
TransitionManager #
The main widget that manages window transitions.
TransitionManager({
required TransitionController controller,
required List<ViewEntry> views,
int initialViewIndex = 0,
Widget? loadingWidget,
AnimationConfig defaultAnimationConfig = AnimationConfig.defaultConfig,
WindowTransitionService? transitionService,
})
Properties:
controller
- Controls the active view and transition stateviews
- List of view configurations and their associated widgetsinitialViewIndex
- Which view to show on startup (default: 0)loadingWidget
- Custom widget shown during initializationdefaultAnimationConfig
- Default animation if not specified per-view
Static Methods:
// Navigate by index
static Future<void> navigateToIndex(
BuildContext context,
int index, {
AnimationConfig? animationConfig,
})
// Navigate by widget type
static Future<void> navigateTo(
BuildContext context,
Widget targetView, {
AnimationConfig? animationConfig,
})
ViewConfig #
Configuration for a single window state.
ViewConfig({
required Size size,
WindowPosition position = WindowPosition.center,
Offset offset = Offset.zero,
EdgeInsets padding = EdgeInsets.zero,
EdgeInsets margin = EdgeInsets.zero,
CustomPositionCalculator? customPositionCalculator,
bool resizable = false,
bool alwaysOnTop = false,
WindowShadow shadow = WindowShadow.platformDefault,
AnimationConfig? animationConfig,
})
Properties:
size
- Window size (absolute pixels or relative 0.0-1.0)position
- Where to position the window (enum)offset
- Additional pixel offset after positioningpadding
- Equal spacing from all screen edgesmargin
- Fine-tuning offset based on anchor pointcustomPositionCalculator
- Custom positioning logicresizable
- Whether user can resize the windowalwaysOnTop
- Keep window above other appsshadow
- Window shadow configurationanimationConfig
- Override default animation for this view
WindowPosition #
enum WindowPosition {
center, // Centered on screen
topLeft, // Top-left corner
topRight, // Top-right corner
bottomLeft, // Bottom-left corner
bottomRight, // Bottom-right corner
centerLeft, // Vertically centered, left edge
centerRight, // Vertically centered, right edge
custom, // Use customPositionCalculator
}
WindowShadow #
// No shadow
WindowShadow.none
// Platform default shadow
WindowShadow.platformDefault
// Custom shadow (for future enhancements)
CustomShadow(
color: Colors.black26,
blurRadius: 15.0,
offset: Offset(0, 8),
spreadRadius: 2.0,
)
AnimationConfig #
// Presets
AnimationConfig.fast() // 250ms
AnimationConfig.defaultConfig // 400ms
AnimationConfig.slow() // 600ms
// Custom
AnimationConfig({
Duration duration = Duration(milliseconds: 400),
Curve curve = Curves.easeInOutCubicEmphasized,
int steps = 25,
Duration stepDuration = Duration(milliseconds: 16),
})
TransitionController #
class TransitionController extends ChangeNotifier {
int get currentViewIndex; // Active view index
bool get isTransitioning; // Is animation in progress?
int get totalViews; // Total number of views
}
Usage with State Management:
// Listen to transitions
controller.addListener(() {
print('Current view: ${controller.currentViewIndex}');
print('Transitioning: ${controller.isTransitioning}');
});
// Disable buttons during transition
ElevatedButton(
onPressed: controller.isTransitioning ? null : () {
// Navigate...
},
child: Text('Switch View'),
)
🎨 Positioning System Explained #
Padding vs Margin #
Padding creates equal spacing from screen edges before positioning:
ViewConfig(
position: WindowPosition.topLeft,
padding: EdgeInsets.all(20), // 20px from all edges
// Window starts at (20, 20)
)
Margin provides additional fine-tuning based on anchor point:
ViewConfig(
position: WindowPosition.topLeft,
padding: EdgeInsets.all(20),
margin: EdgeInsets.only(left: 10, top: 5),
// Window at (20 + 10, 20 + 5) = (30, 25)
)
Position Behavior #
// Center - Equal padding on all sides
WindowPosition.center + padding: EdgeInsets.all(50)
// → Window centered in area 50px smaller on all sides
// TopLeft - Padding + margin from top-left
WindowPosition.topLeft + padding.left/top + margin.left/top
// BottomRight - Padding + margin from bottom-right
WindowPosition.bottomRight + padding.right/bottom + margin.right/bottom
// CenterLeft - Vertically centered, horizontally left
WindowPosition.centerLeft + padding.left + margin.left
// → Vertical centering respects padding.top/bottom
// CenterRight - Vertically centered, horizontally right
WindowPosition.centerRight + padding.right + margin.right
// → Vertical centering respects padding.top/bottom
🔍 Advanced Usage #
Conditional Navigation #
void _handleNavigation() {
if (controller.isTransitioning) {
return; // Ignore if already transitioning
}
final nextIndex = (controller.currentViewIndex + 1) % controller.totalViews;
TransitionManager.navigateToIndex(context, nextIndex);
}
Animation Override #
// Override animation for specific transition
TransitionManager.navigateToIndex(
context,
2,
animationConfig: AnimationConfig(
duration: Duration(milliseconds: 150),
curve: Curves.easeOut,
),
);
📋 Platform Support #
Platform | Supported | Notes |
---|---|---|
macOS | ✅ | Full support with native animations |
Windows | ⏳ | Soon (work in progress) |
Linux | ⏳ | Soon (work in progress) |
Web | ❌ | Not applicable (browser windows) |
Mobile | ❌ | Not applicable (fullscreen apps) |
⚠️ Known Limitations #
- Single Monitor Only - Currently positions windows on the primary display
- No State Persistence - Window configurations aren't saved between sessions (coming in v2.0)
- Shadow Limitations - Custom shadow parameters stored but not yet applied (waiting for window_manager API support)
- No Mid-Transition Cancellation - Transitions must complete before starting another
Uses:
- window_manager for window control
- screen_retriever for screen information