All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.lanterna.gui2.AbstractComponent Maven / Gradle / Ivy

There is a newer version: 3.2.0-alpha1
Show newest version
/*
 * This file is part of lanterna (http://code.google.com/p/lanterna/).
 * 
 * lanterna is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * 
 * Copyright (C) 2010-2020 Martin Berglund
 */
package com.googlecode.lanterna.gui2;

import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.bundle.LanternaThemes;
import com.googlecode.lanterna.graphics.Theme;
import com.googlecode.lanterna.graphics.ThemeDefinition;

/**
 * AbstractComponent provides some good default behaviour for a {@code Component}, all components in Lanterna extends
 * from this class in some way. If you want to write your own component that isn't interactable or theme:able, you
 * probably want to extend from this class.
 * 

* The way you want to declare your new {@code Component} is to pass in itself as the generic parameter, like this: *

 * {@code
 *     public class MyComponent extends AbstractComponent {
 *         ...
 *     }
 * }
 * 
* This was, the component renderer will be correctly setup type-wise and you will need to do fewer typecastings when * you implement the drawing method your new component. * * @author Martin * @param Should always be itself, this value will be used for the {@code ComponentRenderer} declaration */ public abstract class AbstractComponent implements Component { /** * Manually set renderer */ private ComponentRenderer overrideRenderer; /** * If overrideRenderer is not set, this is used instead if not null, set by the theme */ private ComponentRenderer themeRenderer; /** * To keep track of the theme that created the themeRenderer, so we can reset it if the theme changes */ private Theme themeRenderersTheme; /** * If the theme had nothing for this component and no override is set, this is the third fallback */ private ComponentRenderer defaultRenderer; private Container parent; private TerminalSize size; private TerminalSize explicitPreferredSize; //This is keeping the value set by the user (if setPreferredSize() is used) private TerminalPosition position; private Theme themeOverride; private LayoutData layoutData; private boolean invalid; /** * Default constructor */ public AbstractComponent() { size = TerminalSize.ZERO; position = TerminalPosition.TOP_LEFT_CORNER; explicitPreferredSize = null; layoutData = null; invalid = true; parent = null; overrideRenderer = null; themeRenderer = null; themeRenderersTheme = null; defaultRenderer = null; } /** * When you create a custom component, you need to implement this method and return a Renderer which is responsible * for taking care of sizing the component, rendering it and choosing where to place the cursor (if Interactable). * This value is intended to be overridden by custom themes. * @return Renderer to use when sizing and drawing this component */ protected abstract ComponentRenderer createDefaultRenderer(); /** * Takes a {@code Runnable} and immediately executes it if this is called on the designated GUI thread, otherwise * schedules it for later invocation. * @param runnable {@code Runnable} to execute on the GUI thread */ protected void runOnGUIThreadIfExistsOtherwiseRunDirect(Runnable runnable) { if(getTextGUI() != null && getTextGUI().getGUIThread() != null) { getTextGUI().getGUIThread().invokeLater(runnable); } else { runnable.run(); } } /** * Explicitly sets the {@code ComponentRenderer} to be used when drawing this component. This will override whatever * the current theme is suggesting or what the default renderer is. If you call this with {@code null}, the override * is cleared. * @param renderer {@code ComponentRenderer} to be used when drawing this component * @return Itself */ public T setRenderer(ComponentRenderer renderer) { this.overrideRenderer = renderer; return self(); } @Override public synchronized ComponentRenderer getRenderer() { // First try the override if(overrideRenderer != null) { return overrideRenderer; } // Then try to create and return a renderer from the theme Theme currentTheme = getTheme(); if((themeRenderer == null && getBasePane() != null) || // Check if the theme has changed themeRenderer != null && currentTheme != themeRenderersTheme) { themeRenderer = currentTheme.getDefinition(getClass()).getRenderer(selfClass()); if(themeRenderer != null) { themeRenderersTheme = currentTheme; } } if(themeRenderer != null) { return themeRenderer; } // Finally, fallback to the default renderer if(defaultRenderer == null) { defaultRenderer = createDefaultRenderer(); if(defaultRenderer == null) { throw new IllegalStateException(getClass() + " returned a null default renderer"); } } return defaultRenderer; } @Override public void invalidate() { invalid = true; } @Override public synchronized T setSize(TerminalSize size) { this.size = size; return self(); } @Override public TerminalSize getSize() { return size; } @Override public final TerminalSize getPreferredSize() { if(explicitPreferredSize != null) { return explicitPreferredSize; } else { return calculatePreferredSize(); } } @Override public final synchronized T setPreferredSize(TerminalSize explicitPreferredSize) { this.explicitPreferredSize = explicitPreferredSize; return self(); } /** * Invokes the component renderer's size calculation logic and returns the result. This value represents the * preferred size and isn't necessarily what it will eventually be assigned later on. * @return Size that the component renderer believes the component should be */ protected synchronized TerminalSize calculatePreferredSize() { return getRenderer().getPreferredSize(self()); } @Override public synchronized T setPosition(TerminalPosition position) { this.position = position; return self(); } @Override public TerminalPosition getPosition() { return position; } @Override public boolean isInvalid() { return invalid; } @Override public final synchronized void draw(final TextGUIGraphics graphics) { //Delegate drawing the component to the renderer setSize(graphics.getSize()); onBeforeDrawing(); getRenderer().drawComponent(graphics, self()); onAfterDrawing(graphics); invalid = false; } /** * This method is called just before the component's renderer is invoked for the drawing operation. You can use this * hook to do some last-minute adjustments to the component, as an alternative to coding it into the renderer * itself. The component should have the correct size and position at this point, if you call {@code getSize()} and * {@code getPosition()}. */ protected void onBeforeDrawing() { //No operation by default } /** * This method is called immediately after the component's renderer has finished the drawing operation. You can use * this hook to do some post-processing if you need, as an alternative to coding it into the renderer. The * {@code TextGUIGraphics} supplied is the same that was fed into the renderer. * @param graphics Graphics object you can use to manipulate the appearance of the component */ @SuppressWarnings("EmptyMethod") protected void onAfterDrawing(TextGUIGraphics graphics) { //No operation by default } @Override public synchronized T setLayoutData(LayoutData data) { if(layoutData != data) { layoutData = data; invalidate(); } return self(); } @Override public LayoutData getLayoutData() { return layoutData; } @Override public Container getParent() { return parent; } @Override public boolean hasParent(Container parent) { if(this.parent == null) { return false; } Container recursiveParent = this.parent; while(recursiveParent != null) { if(recursiveParent == parent) { return true; } recursiveParent = recursiveParent.getParent(); } return false; } @Override public TextGUI getTextGUI() { if(parent == null) { return null; } return parent.getTextGUI(); } @Override public synchronized Theme getTheme() { if(themeOverride != null) { return themeOverride; } else if(parent != null) { return parent.getTheme(); } else if(getBasePane() != null) { return getBasePane().getTheme(); } else { return LanternaThemes.getDefaultTheme(); } } @Override public ThemeDefinition getThemeDefinition() { return getTheme().getDefinition(getClass()); } @Override public synchronized Component setTheme(Theme theme) { themeOverride = theme; invalidate(); return this; } @Override public boolean isInside(Container container) { Component test = this; while(test.getParent() != null) { if(test.getParent() == container) { return true; } test = test.getParent(); } return false; } @Override public BasePane getBasePane() { if(parent == null) { return null; } return parent.getBasePane(); } @Override public TerminalPosition toBasePane(TerminalPosition position) { Container parent = getParent(); if(parent == null) { return null; } return parent.toBasePane(getPosition().withRelative(position)); } @Override public TerminalPosition toGlobal(TerminalPosition position) { Container parent = getParent(); if(parent == null) { return null; } return parent.toGlobal(getPosition().withRelative(position)); } @Override public synchronized Border withBorder(Border border) { border.setComponent(this); return border; } @Override public synchronized T addTo(Panel panel) { panel.addComponent(this); return self(); } @Override public synchronized void onAdded(Container container) { if (parent != container && parent != null) { // first inform current parent: parent.removeComponent(this); } parent = container; } @Override public synchronized void onRemoved(Container container) { if (parent == container) { parent = null; themeRenderer = null; } else { throw new IllegalStateException(this + " is not " + container +"'s child."); } } /** * This is a little hack to avoid doing typecasts all over the place when having to return {@code T}. Credit to * avl42 for this one! * @return Itself, but as type T */ @SuppressWarnings("unchecked") protected T self() { return (T)this; } @SuppressWarnings("unchecked") private Class selfClass() { return (Class)getClass(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy