Skip to content

Commit

Permalink
feat: introduce widgets api
Browse files Browse the repository at this point in the history
also add a new example that uses it!
  • Loading branch information
benthillerkus committed Apr 8, 2022
1 parent 743a2b5 commit 74c1d31
Show file tree
Hide file tree
Showing 39 changed files with 1,918 additions and 146 deletions.
5 changes: 1 addition & 4 deletions README.md
Expand Up @@ -30,11 +30,8 @@ samples, guidance on mobile development, and a full API reference.

- FIXME Find out all possible errors and repackage / handle them
- TODO Write documentation
- TODO Support images
- TODO Find out, communicate and memoize the correct system metrics (icon resolution)
- TODO Find out, communicate and memoize the correct system metrics (icon resolution)
- TODO Support interaction
- TODO Add function to configure a TrayIcon in one call (maybe via a dynamic map actually)
- FIXME Use OnWidgetUpdated or smth like that to diff changes between widget dependencies and flush them to the plugin
- TODO Logging

## Style
Expand Down
73 changes: 72 additions & 1 deletion example/README.md
@@ -1 +1,72 @@
# Examples
# How to use?

To add an icon to your app, simply use the [`TrayIconWidget`](../lib/src/widgets.dart).

```dart
import 'package:betrayal/betrayal.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Look at the system tray 👀")
),
body: Center(
child: TrayIconWidget(
winIcon: WinIcon.application,
tooltip: "Here I am!"
child: FlutterLogo()
)
)
)
);
}
```

Check out the [Widgets Api](widget_api/lib/main.dart) example to learn more about this approach.

## Ok, but I want to work with `TrayIcon`s directly, please!

Sure, you can use the [imperative api](../lib/src/imperative.dart) to create and control `TrayIcon`s directly.

```dart
final icon = TrayIcon();
await icon.setTooltip("🎭");
await icon.setImage(asset: "assets/dart.ico"));
await icon.show();
```

Just make sure to dispose of it and free its resources once you're done.

```dart
icon.dispose();
```

If you don't feel like managing an icon, you can still use a `TrayIconWidget` and get its underlying `TrayIcon` by calling `TrayIcon.of(context)`.

```dart
@override
Widget build(BuildContext context) {
return TrayIconWidget(
child:
// .............
Builder(
builder: (BuildContext context) {
// The BuildContext must be from a child of `TrayIconWidget`,
// otherwise the icon may not be found.
return IconButton(
onPressed: () => TrayIcon.of(context).setTooltip("🙇‍♂️"),
icon: Icon(Icons.add)
);
}
)
);
}
```
115 changes: 55 additions & 60 deletions example/add_many/lib/main.dart
Expand Up @@ -2,13 +2,27 @@ import 'dart:math';
import 'dart:ui';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:betrayal/betrayal.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class Lol extends CustomPainter {
ui.Image img;

Lol(this.img);

@override
void paint(Canvas canvas, Size size) {
canvas.drawImage(img, Offset.zero, Paint());
}

@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);

Expand All @@ -17,11 +31,6 @@ class MyApp extends StatefulWidget {
}

class _MyAppState extends State<MyApp> {
// // If the widget was removed from the tree while the asynchronous platform
// // message was in flight, we want to discard the reply rather than calling
// // setState to update our non-existent appearance.
// if (!mounted) return;

final _icons = List<TrayIcon>.empty(growable: true);

ui.Image? img;
Expand All @@ -35,58 +44,14 @@ class _MyAppState extends State<MyApp> {

setState(() {});

icon.setIcon(
icon.setImage(
pixels:
(await img?.toByteData(format: ui.ImageByteFormat.rawRgba))?.buffer,
// asset: "assets/flutter.ico",
winIcon: WinIcon.shield);
icon.show();
}

Future<void> draw() async {
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
const center = Offset(16, 16);
final Paint paint = Paint()..color = const Color(0xFFF400BB);
canvas.drawRect(const Rect.fromLTWH(0, 0, 16, 16), paint);
canvas.drawRect(
const Rect.fromLTWH(16, 0, 16, 16), paint..color = Colors.green);
canvas.drawRect(
const Rect.fromLTWH(0, 16, 16, 16), paint..color = Colors.blue);
canvas.drawCircle(center, sqrt(_icons.length.ceilToDouble()),
paint..color = const Color.fromARGB(255, 211, 168, 202));
TextPainter(
text: TextSpan(text: "${_icons.length}"),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
maxLines: 1)
..layout()
..paint(canvas, Offset.zero);
canvas.drawRect(
const Rect.fromLTWH(1, 0, 1, 1), paint..color = Colors.green);
canvas.drawRect(
const Rect.fromLTWH(2, 0, 1, 1), paint..color = Colors.blue);

final picture = recorder.endRecording();

img = await picture.toImage(32, 32);
}

void remove() async {
_icons.removeLast().dispose();
await draw();
setState(() {});
}

void removeAll() async {
for (var icon in _icons) {
icon.dispose();
}
_icons.clear();
await draw();
setState(() {});
}

@override
Widget build(BuildContext context) {
return MaterialApp(
Expand Down Expand Up @@ -154,18 +119,48 @@ class _MyAppState extends State<MyApp> {
),
);
}
}

class Lol extends CustomPainter {
Lol(this.img);
Future<void> draw() async {
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
const center = Offset(16, 16);
final Paint paint = Paint()..color = const Color(0xFFF400BB);
canvas.drawRect(const Rect.fromLTWH(0, 0, 16, 16), paint);
canvas.drawRect(
const Rect.fromLTWH(16, 0, 16, 16), paint..color = Colors.green);
canvas.drawRect(
const Rect.fromLTWH(0, 16, 16, 16), paint..color = Colors.blue);
canvas.drawCircle(center, sqrt(_icons.length.ceilToDouble()),
paint..color = const Color.fromARGB(255, 211, 168, 202));
TextPainter(
text: TextSpan(text: "${_icons.length}"),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
maxLines: 1)
..layout()
..paint(canvas, Offset.zero);
canvas.drawRect(
const Rect.fromLTWH(1, 0, 1, 1), paint..color = Colors.green);
canvas.drawRect(
const Rect.fromLTWH(2, 0, 1, 1), paint..color = Colors.blue);

ui.Image img;
final picture = recorder.endRecording();

@override
void paint(Canvas canvas, Size size) {
canvas.drawImage(img, Offset.zero, Paint());
img = await picture.toImage(32, 32);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
void remove() async {
_icons.removeLast().dispose();
await draw();
setState(() {});
}

void removeAll() async {
for (var icon in _icons) {
icon.dispose();
}
_icons.clear();
await draw();
setState(() {});
}
}
54 changes: 27 additions & 27 deletions example/edit_icon/lib/main.dart
Expand Up @@ -13,6 +13,13 @@ void main() {
runApp(const MyApp());
}

class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

Expand All @@ -34,23 +41,16 @@ class MyApp extends StatelessWidget {
}
}

class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class MyData extends SelectableData {
TrayIconImageDelegate delegate;

MyData(
{Widget? Function(BuildContext)? builder,
Key? key,
String? name,
required this.delegate})
: super(builder: builder, key: key, name: name);

TrayIconImageDelegate delegate;

@override
MyData copyWith(
{Widget? Function(BuildContext context)? builder,
Expand All @@ -66,24 +66,6 @@ class MyData extends SelectableData {
class _HomeScreenState extends State<HomeScreen> {
late final TrayIcon _icon = TrayIcon();

@override
void initState() {
super.initState();
_setTrayIcon(_delegate.elementAt(0));
}

@override
void dispose() {
_icon.dispose();
super.dispose();
}

void _setTrayIcon(MyData element) {
_icon.setTooltip(element.name ?? element.key.toString());
_icon.setIcon(delegate: element.delegate);
_icon.show();
}

late final _delegate = ElementSelectorDelegate(
onEmptied: _icon.hide,
onElementChanged: (MyData element) {
Expand Down Expand Up @@ -147,4 +129,22 @@ class _HomeScreenState extends State<HomeScreen> {
},
));
}

@override
void dispose() {
_icon.dispose();
super.dispose();
}

@override
void initState() {
super.initState();
_setTrayIcon(_delegate.elementAt(0));
}

void _setTrayIcon(MyData element) {
_icon.setTooltip(element.name ?? element.key.toString());
_icon.setImage(delegate: element.delegate);
_icon.show();
}
}
46 changes: 46 additions & 0 deletions example/widgets_api/.gitignore
@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
10 changes: 10 additions & 0 deletions example/widgets_api/.metadata
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: c860cba910319332564e1e9d470a17074c1f2dfd
channel: stable

project_type: app

0 comments on commit 74c1d31

Please sign in to comment.