/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.utils.text.dynamic; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.TimeScale; import com.jcwhatever.nucleus.utils.text.components.IChatMessage; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; /** * An {@link IDynamicText} implementation that can be used to * queue text to be displayed for specified amounts * of time in the order that they are queued. * * <p>Can also add {@link java.lang.Runnable}'s to the queue as well as * insert pauses between queued text and {@link java.lang.Runnable}'s.</p> */ public class QueuedText implements IDynamicText { private final Deque<Pause> _pauses = new ArrayDeque<>(10); private final Deque<Runnable> _onEmpty = new ArrayDeque<>(10); private volatile long _nextUpdate; private volatile IDynamicText _currentText; private final Object _sync = new Object(); /** * Constructor. */ public QueuedText() {} /** * Determine if there are text events queued. */ public boolean isEmpty() { synchronized (_sync) { return _pauses.isEmpty(); } } /** * Get the number of queued text events. */ public int size() { synchronized (_sync) { return _pauses.size(); } } /** * Get a builder used to add queued events to the {@link QueuedText} * instance. */ public QueueTextBuilder getBuilder() { return new QueueTextBuilder(); } @Override public IChatMessage nextText() { if (_nextUpdate != 0 && _nextUpdate >= System.currentTimeMillis()) { return _currentText != null ? _currentText.nextText() : null; } Action runAction = null; synchronized (_sync) { if (isEmpty()) { while (!_onEmpty.isEmpty()) { Runnable onEmpty = _onEmpty.removeFirst(); onEmpty.run(); } return null; } Pause pause = _pauses.removeFirst(); _nextUpdate = System.currentTimeMillis() + pause.duration; if (pause instanceof Text) { Text text = (Text) pause; return (_currentText = text.text).nextText(); } if (pause instanceof Action) { runAction = (Action) pause; } } if (runAction != null) { runAction.runnable.run(); } return null; } @Override public int getRefreshRate() { return isEmpty() ? 0 : 1; } @Override public String toString() { return nextText().toString(); } public class QueueTextBuilder { List<Pause> pauses = new ArrayList<>(10); List<Runnable> onEmpty = new ArrayList<>(3); /** * Add text to the queue. * * @param duration The duration the text is displayed for. * @param text The text to display. * * @return Self for chaining. */ public QueueTextBuilder text(int duration, CharSequence text) { PreCon.positiveNumber(duration); PreCon.notNull(text); pauses.add(new Text(duration, new DynamicTextBuilder().append(text).build())); return this; } /** * Add dynamic text to the queue. * * @param duration The duration the text is displayed for. * @param text The text to display. * * @return Self for chaining. */ public QueueTextBuilder text(int duration, IDynamicText text) { PreCon.positiveNumber(duration); PreCon.notNull(text); pauses.add(new Text(duration, text)); return this; } /** * Add a {@link Runnable} instance to run. * * @param duration The duration to pause before moving to the next queue item. * @param runnable The runnable to run. * * @return Self for chaining. */ public QueueTextBuilder run(int duration, Runnable runnable) { PreCon.positiveNumber(duration); PreCon.notNull(runnable); pauses.add(new Action(duration, runnable)); return this; } /** * Add a pause duration to the queue. * * @param duration The duration of the pause. * * @return Self for chaining. */ public QueueTextBuilder pause(int duration) { PreCon.positiveNumber(duration); pauses.add(new Pause(duration)); return this; } /** * Add a one time {@link Runnable} instance to run * when the queue is empty. * * @param runnable The runnable. * * @return Self for chaining. */ public QueueTextBuilder onEmpty(Runnable runnable) { PreCon.notNull(runnable); onEmpty.add(runnable); return this; } /** * Build and append to the parent {@link QueuedText} queue. */ public void build() { synchronized (_sync) { _pauses.addAll(pauses); pauses.clear(); _onEmpty.addAll(onEmpty); onEmpty.clear(); } } } private static class Text extends Pause { final IDynamicText text; Text(int duration, IDynamicText text) { super(duration); this.text = text; } } private static class Action extends Pause { final Runnable runnable; Action(int duration, Runnable runnable) { super(duration); this.runnable = runnable; } } private static class Pause { final int duration; // milliseconds Pause (int duration) { this.duration = duration * TimeScale.TICKS.getTimeFactor(); } } }