![JAR search and dependency download from the Maven repository](/logo.png)
com.googlecode.lanterna.gui2.Panel Maven / Gradle / Ivy
/*
* 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.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.input.KeyStroke;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* This class is the basic building block for creating user interfaces, being the standard implementation of
* {@code Container} that supports multiple children. A {@code Panel} is a component that can contain one or more
* other components, including nested panels. The panel itself doesn't have any particular appearance and isn't
* interactable by itself, although you can set a border for the panel and interactable components inside the panel will
* receive input focus as expected.
*
* @author Martin
*/
public class Panel extends AbstractComponent implements Container {
private final List components;
private LayoutManager layoutManager;
private TerminalSize cachedPreferredSize;
private TextColor fillColorOverride;
/**
* Default constructor, creates a new panel with no child components and by default set to a vertical
* {@code LinearLayout} layout manager.
*/
public Panel() {
this(new LinearLayout());
}
public Panel(LayoutManager layoutManager) {
if(layoutManager == null) {
layoutManager = new AbsoluteLayout();
}
this.components = new ArrayList();
this.layoutManager = layoutManager;
this.cachedPreferredSize = null;
}
/**
* Adds a new child component to the panel. Where within the panel the child will be displayed is up to the layout
* manager assigned to this panel. If the component has already been added to another panel, it will first be
* removed from that panel before added to this one.
* @param component Child component to add to this panel
* @return Itself
*/
public Panel addComponent(Component component) {
return addComponent(Integer.MAX_VALUE, component);
}
/**
* Adds a new child component to the panel. Where within the panel the child will be displayed is up to the layout
* manager assigned to this panel. If the component has already been added to another panel, it will first be
* removed from that panel before added to this one.
* @param component Child component to add to this panel
* @param index At what index to add the component among the existing components
* @return Itself
*/
public Panel addComponent(int index, Component component) {
if(component == null) {
throw new IllegalArgumentException("Cannot add null component");
}
synchronized(components) {
if(components.contains(component)) {
return this;
}
if(component.getParent() != null) {
component.getParent().removeComponent(component);
}
if (index > components.size()) {
index = components.size();
}
else if (index < 0) {
index = 0;
}
components.add(index, component);
}
component.onAdded(this);
invalidate();
return this;
}
/**
* This method is a shortcut for calling:
*
* {@code
* component.setLayoutData(layoutData);
* panel.addComponent(component);
* }
*
* @param component Component to add to the panel
* @param layoutData Layout data to assign to the component
* @return Itself
*/
public Panel addComponent(Component component, LayoutData layoutData) {
if(component != null) {
component.setLayoutData(layoutData);
addComponent(component);
}
return this;
}
@Override
public boolean containsComponent(Component component) {
return component != null && component.hasParent(this);
}
@Override
public boolean removeComponent(Component component) {
if(component == null) {
throw new IllegalArgumentException("Cannot remove null component");
}
synchronized(components) {
int index = components.indexOf(component);
if(index == -1) {
return false;
}
if(getBasePane() != null && getBasePane().getFocusedInteractable() == component) {
getBasePane().setFocusedInteractable(null);
}
components.remove(index);
}
component.onRemoved(this);
invalidate();
return true;
}
/**
* Removes all child components from this panel
* @return Itself
*/
public Panel removeAllComponents() {
synchronized(components) {
for(Component component : new ArrayList(components)) {
removeComponent(component);
}
}
return this;
}
/**
* Assigns a new layout manager to this panel, replacing the previous layout manager assigned. Please note that if
* the panel is not empty at the time you assign a new layout manager, the existing components might not show up
* where you expect them and their layout data property might need to be re-assigned.
* @param layoutManager New layout manager this panel should be using
* @return Itself
*/
public synchronized Panel setLayoutManager(LayoutManager layoutManager) {
if(layoutManager == null) {
layoutManager = new AbsoluteLayout();
}
this.layoutManager = layoutManager;
invalidate();
return this;
}
/**
* Returns the color used to override the default background color from the theme, if set. Otherwise {@code null} is
* returned and whatever theme is assigned will be used to derive the fill color.
* @return The color, if any, used to fill the panel's unused space instead of the theme's color
*/
public TextColor getFillColorOverride() {
return fillColorOverride;
}
/**
* Sets an override color to be used instead of the theme's color for Panels when drawing unused space. If called
* with {@code null}, it will reset back to the theme's color.
* @param fillColor Color to draw the unused space with instead of what the theme definition says, no {@code null}
* to go back to the theme definition
*/
public void setFillColorOverride(TextColor fillColor) {
this.fillColorOverride = fillColorOverride;
}
/**
* Returns the layout manager assigned to this panel
* @return Layout manager assigned to this panel
*/
public LayoutManager getLayoutManager() {
return layoutManager;
}
@Override
public int getChildCount() {
synchronized(components) {
return components.size();
}
}
@Override
public Collection getChildren() {
synchronized(components) {
return new ArrayList(components);
}
}
@Override
protected ComponentRenderer createDefaultRenderer() {
return new DefaultPanelRenderer();
}
public class DefaultPanelRenderer implements ComponentRenderer {
private boolean fillAreaBeforeDrawingComponents = true;
/**
* If setting this to {@code false} (default is {@code true}), the {@link Panel} will not reset it's drawable
* area with the space character ' ' before drawing all the components. Usually you do want to reset this
* area before drawing but you might have a custom renderer that has prepared the area already and just want the
* panel renderer to layout and draw the components in the panel without touching the existing content. One such
* example is the {@code FullScreenTextGUITest}.
* @param fillAreaBeforeDrawingComponents Should the panels area be cleared before drawing components?
*/
public void setFillAreaBeforeDrawingComponents(boolean fillAreaBeforeDrawingComponents) {
this.fillAreaBeforeDrawingComponents = fillAreaBeforeDrawingComponents;
}
@Override
public TerminalSize getPreferredSize(Panel component) {
synchronized(components) {
cachedPreferredSize = layoutManager.getPreferredSize(components);
}
return cachedPreferredSize;
}
@Override
public void drawComponent(TextGUIGraphics graphics, Panel panel) {
if(isInvalid()) {
layout(graphics.getSize());
}
if (fillAreaBeforeDrawingComponents) {
// Reset the area
graphics.applyThemeStyle(getThemeDefinition().getNormal());
if (fillColorOverride != null) {
graphics.setBackgroundColor(fillColorOverride);
}
graphics.fill(' ');
}
synchronized(components) {
for(Component child: components) {
TextGUIGraphics componentGraphics = graphics.newTextGraphics(child.getPosition(), child.getSize());
child.draw(componentGraphics);
}
}
}
}
@Override
public TerminalSize calculatePreferredSize() {
if(cachedPreferredSize != null && !isInvalid()) {
return cachedPreferredSize;
}
return super.calculatePreferredSize();
}
@Override
public boolean isInvalid() {
synchronized(components) {
for(Component component: components) {
if(component.isInvalid()) {
return true;
}
}
}
return super.isInvalid() || layoutManager.hasChanged();
}
@Override
public Interactable nextFocus(Interactable fromThis) {
boolean chooseNextAvailable = (fromThis == null);
synchronized(components) {
for(Component component : components) {
if(chooseNextAvailable) {
if(component instanceof Interactable && ((Interactable) component).isEnabled() && ((Interactable) component).isFocusable()) {
return (Interactable) component;
}
else if(component instanceof Container) {
Interactable firstInteractable = ((Container) (component)).nextFocus(null);
if(firstInteractable != null) {
return firstInteractable;
}
}
continue;
}
if(component == fromThis) {
chooseNextAvailable = true;
continue;
}
if(component instanceof Container) {
Container container = (Container) component;
if(fromThis.isInside(container)) {
Interactable next = container.nextFocus(fromThis);
if(next == null) {
chooseNextAvailable = true;
}
else {
return next;
}
}
}
}
return null;
}
}
@Override
public Interactable previousFocus(Interactable fromThis) {
boolean chooseNextAvailable = (fromThis == null);
List revComponents = new ArrayList();
synchronized(components) {
revComponents.addAll(components);
}
Collections.reverse(revComponents);
for (Component component : revComponents) {
if (chooseNextAvailable) {
if (component instanceof Interactable && ((Interactable)component).isEnabled() && ((Interactable)component).isFocusable()) {
return (Interactable) component;
}
if (component instanceof Container) {
Interactable lastInteractable = ((Container)(component)).previousFocus(null);
if (lastInteractable != null) {
return lastInteractable;
}
}
continue;
}
if (component == fromThis) {
chooseNextAvailable = true;
continue;
}
if (component instanceof Container) {
Container container = (Container) component;
if (fromThis.isInside(container)) {
Interactable next = container.previousFocus(fromThis);
if (next == null) {
chooseNextAvailable = true;
} else {
return next;
}
}
}
}
return null;
}
@Override
public boolean handleInput(KeyStroke key) {
return false;
}
@Override
public void updateLookupMap(InteractableLookupMap interactableLookupMap) {
synchronized(components) {
for(Component component: components) {
if(component instanceof Container) {
((Container)component).updateLookupMap(interactableLookupMap);
}
else if(component instanceof Interactable && ((Interactable)component).isEnabled() && ((Interactable)component).isFocusable()) {
interactableLookupMap.add((Interactable)component);
}
}
}
}
@Override
public void invalidate() {
super.invalidate();
synchronized(components) {
//Propagate
for(Component component: components) {
component.invalidate();
}
}
}
private void layout(TerminalSize size) {
synchronized(components) {
layoutManager.doLayout(size, components);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy