blob: e3e81e07c22b06d3b4f8ff2af07fc9624ffbd9fd [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:observatory/src/elements/helpers/rendering_queue.dart';
export 'package:observatory/src/elements/helpers/rendering_queue.dart';
/// A generic renderable object.
abstract class Renderable {
void render();
}
/// Event related to a Renderable rendering phase.
class RenderedEvent<T> {
/// Renderable to which the event is related
final T element;
/// Is another rendering scheduled for this element.
final bool otherRenderScheduled;
RenderedEvent(this.element, this.otherRenderScheduled) {
assert(element != null);
assert(otherRenderScheduled != null);
}
}
/// Scheduler for rendering operations.
class RenderingScheduler<T extends Renderable> implements RenderingTask {
bool _enabled = false;
bool _dirty = false;
bool _renderingScheduled = false;
bool _notificationScheduled = false;
/// Element managed by this scheduler.
final T element;
/// Queue used for rendering operations.
final RenderingQueue queue;
final List<Future> _wait = <Future>[];
/// Does the element need a new rendering cycle.
bool get isDirty => _dirty;
/// Is the scheduler enabled.
bool get isEnabled => _enabled;
final StreamController<RenderedEvent<T>> _onRendered =
new StreamController<RenderedEvent<T>>.broadcast();
Stream<RenderedEvent<T>> get onRendered => _onRendered.stream;
/// Creates a new scheduler for an element.
/// If no queue is provided it will create a new default configured queue.
factory RenderingScheduler(T element, {RenderingQueue queue}) {
assert(element != null);
if (queue == null) {
queue = new RenderingQueue();
}
return new RenderingScheduler<T>._(element, queue);
}
RenderingScheduler._(this.element, this.queue);
/// Enable the scheduler.
/// New dirty or schedule request will be considered.
void enable() {
if (_enabled) return;
_enabled = true;
scheduleRendering();
}
/// Disable the scheduler.
/// New dirty or schedule request will be discarded.
/// [optional] notify: send a final RenderEvent.
void disable({bool notify: false}) {
assert(notify != null);
if (!_enabled) return;
_enabled = false;
if (notify) scheduleNotification();
}
/// Set the object as dirty. A rendering will be scheduled.
void dirty() {
if (_dirty) return;
_dirty = true;
scheduleRendering();
}
/// Checks for modification during attribute set.
/// If value changes a new rendering is scheduled.
/// set attr(T v) => _attr = _r.checkAndReact(_attr, v);
dynamic checkAndReact(dynamic oldValue, dynamic newValue) {
if (oldValue != newValue)
dirty();
else
scheduleNotification();
return newValue;
}
/// Schedules a new rendering phase.
void scheduleRendering() {
if (_renderingScheduled) return;
if (!_enabled) return;
queue.enqueue(this);
_renderingScheduled = true;
}
/// Renders the element (if the scheduler is enabled).
/// It will clear the dirty flag.
void render() {
if (!_enabled) return;
_dirty = false;
_wait.clear();
element.render();
_renderingScheduled = false;
scheduleNotification();
if (_dirty) scheduleRendering();
}
/// Schedules a notification.
void scheduleNotification() {
if (_notificationScheduled) return;
_notify();
_notificationScheduled = true;
}
void waitFor(Iterable<Future> it) {
_wait.addAll(it);
}
Future _notify() async {
await Future.wait(_wait);
_wait.clear();
_onRendered.add(new RenderedEvent<T>(element, _dirty));
_notificationScheduled = false;
}
}