Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
- Add FPS counter, 1h
- Fix background size when window size changed, 2h
- Host on GCP, 2h
- Move engine to inner package, 2h
-
6 changes: 3 additions & 3 deletions lib/actors/cell/body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ class Body extends Actor {
final double rotationSpeed =
math.Random.secure().nextDouble() * 0.4 * math.pi;

void setTired(double tire) {
final blackAlpha = (tire * 0xFF).floor();
void setTired(double tired) {
final blackAlpha = (tired * 0xFF).floor();
colorFilter = ColorFilter.mode(
Colors.black.withAlpha(blackAlpha),
Colors.blue.withAlpha(blackAlpha),
BlendMode.srcIn,
);
}
Expand Down
3 changes: 3 additions & 0 deletions lib/actors/cell/cell.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Cell extends Actor {

body.setTired(tired);
eye.setTired(tired);
_setTired(tired);

_approachManna(root);

Expand All @@ -72,6 +73,8 @@ class Cell extends Actor {
super.update(root, millis);
}

void _setTired(double tired) => opacity = 1.0 - tired;

void _processEating(Actor root) {} // todo _processEating

void _approachManna(Actor root) {
Expand Down
6 changes: 3 additions & 3 deletions lib/actors/cell/eye.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class Eye extends Actor {
image: Assets.instance.eyeImage,
);

void setTired(double tire) {
final whiteAlpha = ((1 - tire) * 0xFF).floor();
final redAlpha = (tire * 0x80).floor();
void setTired(double tired) {
final whiteAlpha = ((1 - tired) * 0xFF).floor();
final redAlpha = (tired * 0x80).floor();
colorFilter = ColorFilter.mode(
Color.alphaBlend(
Colors.white.withAlpha(whiteAlpha),
Expand Down
6 changes: 4 additions & 2 deletions lib/di/di.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'dart:async';

import 'package:digital_clock/theme/theme_select.dart';
import 'package:digital_clock/theme/theme_select_cubit.dart';
import 'package:digital_clock/utils/fps.dart';
import 'package:get_it/get_it.dart';

final GetIt di = GetIt.I;

void diInit() {
di.registerLazySingleton<ThemeSelect>(ThemeSelect.new);
di.registerLazySingleton<Fps>(Fps.new);
di.registerLazySingleton<ThemeSelectCubit>(ThemeSelectCubit.new);
}

void diDispose() {
Expand Down
11 changes: 6 additions & 5 deletions lib/digital_clock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:digital_clock/log/log.dart';
import 'package:digital_clock/petri_dish.dart';
import 'package:digital_clock/providers.dart';
import 'package:digital_clock/settings_panel.dart';
import 'package:digital_clock/theme/theme_select.dart';
import 'package:digital_clock/theme/theme_select_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

Expand All @@ -26,10 +26,10 @@ class Clock extends StatelessWidget {

@override
Widget build(BuildContext context) => Providers(
child: BlocBuilder<ThemeSelect, ThemeMode>(
child: BlocBuilder<ThemeSelectCubit, ThemeMode>(
builder: (context, themeMode) => MaterialApp(
theme: ThemeSelect.light,
darkTheme: ThemeSelect.dark,
theme: ThemeSelectCubit.light,
darkTheme: ThemeSelectCubit.dark,
themeMode: themeMode,
home: const Scaffold(
body: Stack(
Expand All @@ -39,7 +39,7 @@ class Clock extends StatelessWidget {
child: BackgroundLife(),
),
Opacity(
opacity: 0.5,
opacity: 0.6,
child: DigitalClock(),
),
Align(
Expand Down Expand Up @@ -73,6 +73,7 @@ class BackgroundLife extends StatelessWidget {
SizedBox.expand(
child: PetriDish(
name: 'background',
calculateFps: true,
valueNotifier: ValueNotifier(10),
key: const Key('background'),
sizeX: width,
Expand Down
4 changes: 4 additions & 0 deletions lib/engine/actor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class Actor implements Update, Draw {
this.velocityAngle = 0,
this.colorFilter,
this.image,
this.opacity = 0.4,
}) : scale = scale ?? Vector.one(),
pivot = pivot ?? Vector(x: size.x / 2, y: size.y / 2),
angle = angle ?? velocityAngle;
Expand All @@ -46,6 +47,7 @@ abstract class Actor implements Update, Draw {
ColorFilter? colorFilter;
ui.Image? image;
Offset acceleration = Offset.zero;
double opacity;

double maxRotationSpeed = _kRotationSpeed;

Expand Down Expand Up @@ -88,6 +90,7 @@ abstract class Actor implements Update, Draw {
),
colorFilter: colorFilter,
filterQuality: Config.instance.filterQuality,
opacity: opacity,
image: image!,
);
}
Expand Down Expand Up @@ -141,5 +144,6 @@ abstract class Actor implements Update, Draw {
'velocityAngle: $velocityAngle, '
'scale: $scale, '
'angle: $angle, '
'opacity: $opacity, '
'children: $children}';
}
50 changes: 43 additions & 7 deletions lib/fps_panel.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
import 'dart:async';

import 'package:digital_clock/di/di.dart';
import 'package:digital_clock/utils/fps.dart';
import 'package:flutter/material.dart';

class FpsPanel extends StatelessWidget {
const FpsPanel({
super.key,
});
class FpsPanel extends StatefulWidget {
const FpsPanel({super.key});

@override
State<FpsPanel> createState() => _FpsPanelState();
}

class _FpsPanelState extends State<FpsPanel> {
late final _fps = di<Fps>();
late final Timer _timer;

@override
void initState() {
super.initState();

_timer = Timer.periodic(
const Duration(seconds: 1),
(_) {
if (!mounted) {
return;
}

setState(() {});
},
);
}

@override
void dispose() {
_timer.cancel();

super.dispose();
}

@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(4),
child: Text(
'0 fps',
style: Theme.of(context).textTheme.bodySmall,
'${_fps.result} fps',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.grey.withAlpha(0x80)),
),
);
}
10 changes: 5 additions & 5 deletions lib/petri_dish.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import 'dart:async';
import 'package:digital_clock/actors/cell/cell.dart';
import 'package:digital_clock/actors/scene/scene.dart';
import 'package:digital_clock/actors/scene/scene_settings.dart';
import 'package:digital_clock/di/di.dart';
import 'package:digital_clock/engine/actor.dart';
import 'package:digital_clock/engine/vector.dart';
import 'package:digital_clock/log/log.dart';
import 'package:digital_clock/utils/delta.dart';
import 'package:digital_clock/utils/fps.dart';
import 'package:flutter/material.dart';

const int kMinFrameDelta = 33;
const kMinFrameDelta = 33;

class PetriDish extends StatefulWidget {
const PetriDish({
Expand Down Expand Up @@ -125,8 +126,7 @@ class DrawingWidget extends StatefulWidget {
class _DrawingWidgetState extends State<DrawingWidget>
with WidgetsBindingObserver {
Delta delta = Delta();
late final Fps fps = Fps();
double lastFps = 0;
late final _fps = di<Fps>();
late final Timer fpsDisplayTimer;
final StreamController<void> updateDisplay =
StreamController<void>.broadcast();
Expand All @@ -139,7 +139,7 @@ class _DrawingWidgetState extends State<DrawingWidget>
if (widget.calculateFps) {
fpsDisplayTimer = Timer.periodic(const Duration(seconds: 10), (_) {
final cells = widget.scene.children.whereType<Cell>().length;
debugPrint('fps=${lastFps.round()}, cells=$cells');
debugPrint('fps=${_fps.result}, cells=$cells');
});
}

Expand Down Expand Up @@ -184,7 +184,7 @@ class _DrawingWidgetState extends State<DrawingWidget>
}

if (widget.calculateFps) {
lastFps = fps.calculate(deltaMillis);
_fps.update(deltaMillis.toDouble());
}

return CustomPaint(
Expand Down
4 changes: 2 additions & 2 deletions lib/providers.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:digital_clock/theme/theme_select.dart';
import 'package:digital_clock/theme/theme_select_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

Expand All @@ -13,7 +13,7 @@ class Providers extends StatelessWidget {
@override
Widget build(BuildContext context) => MultiBlocProvider(
providers: [
BlocProvider(create: (_) => ThemeSelect()),
BlocProvider(create: (_) => ThemeSelectCubit()),
],
child: child,
);
Expand Down
25 changes: 19 additions & 6 deletions lib/settings_panel.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import 'package:digital_clock/theme/theme_select.dart';
import 'package:digital_clock/theme/durations.dart';
import 'package:digital_clock/theme/theme_select_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SettingsPanel extends StatelessWidget {
const SettingsPanel({
super.key,
});
const SettingsPanel({super.key});

@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: () => context.read<ThemeSelect>().toggleBrightness(),
icon: const Icon(Icons.dark_mode),
onPressed: context.read<ThemeSelectCubit>().toggleMode,
icon: BlocBuilder<ThemeSelectCubit, ThemeMode>(
builder: (context, state) => AnimatedSwitcher(
duration: AnimationDurations.s.value,
child: Icon(
_getIcon(state),
key: ValueKey(state),
),
),
),
iconSize: 32,
),
);

IconData _getIcon(ThemeMode themeMode) => switch (themeMode) {
ThemeMode.system => Icons.brightness_auto_outlined,
ThemeMode.light => Icons.light_mode_outlined,
ThemeMode.dark => Icons.dark_mode_outlined,
};
}
11 changes: 11 additions & 0 deletions lib/theme/durations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
enum AnimationDurations {
xs(Duration(milliseconds: 200)),
s(Duration(milliseconds: 300)),
m(Duration(milliseconds: 500)),
l(Duration(milliseconds: 800)),
xl(Duration(milliseconds: 1200));

const AnimationDurations(this.value);

final Duration value;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class ThemeSelect extends Cubit<ThemeMode> {
ThemeSelect() : super(ThemeMode.system);
class ThemeSelectCubit extends Cubit<ThemeMode> {
ThemeSelectCubit() : super(ThemeMode.system);

static final ThemeData dark = ThemeData.dark().copyWith(
canvasColor: Colors.black,
Expand All @@ -12,7 +12,7 @@ class ThemeSelect extends Cubit<ThemeMode> {
canvasColor: Colors.white,
);

void toggleBrightness() {
void toggleMode() {
switch (state) {
case ThemeMode.dark:
setLight();
Expand Down
24 changes: 11 additions & 13 deletions lib/utils/fps.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
const int _kAveragingLength = 30;
import 'package:digital_clock/utils/math/ema.dart';

class Fps {
final List<double> _averaging = <double>[];
Fps({
int n = 180,
}) : _ema = ExponentialMovingAverage(n: n);

double calculate(int delta) {
_averaging.add(delta.toDouble());
while (_averaging.length > _kAveragingLength) {
_averaging.removeAt(0);
}

final averageDelay =
_averaging.reduce((double a, double b) => a + b) / _averaging.length;

final fps = (averageDelay == 0) ? 0.0 : (1000.0 / averageDelay);
final ExponentialMovingAverage _ema;

return fps;
void update(double delta) {
if (delta != 0) {
_ema.update(1000 / delta);
}
}

int get result => _ema.ema.floor();
}
Loading