diff --git a/README.md b/README.md index 9e44e82..9ab887d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,42 @@ # PanelStudio A simple yet flexible library to create ClickGUIs designed for use in Minecraft utility mods. It was originally designed for a private client, but made open source, so that it could be used for [GameSense](https://github.com/IUDevman/gamesense-client). Here are some screenshots of what is possible with this library: * CyberHack Theme: -![cyberhack](https://cdn.discordapp.com/attachments/755077474861449277/770697901499744286/2020-10-27_18.16.50.png) +![cyberhack](https://cdn.discordapp.com/attachments/747111616407011389/779996603510947870/2020-11-21_20.23.26.png) * GameSense 2.0 Theme: -![gamesense20](https://cdn.discordapp.com/attachments/755077474861449277/770697937234821170/2020-10-27_18.16.59.png) +![gamesense20](https://cdn.discordapp.com/attachments/747111616407011389/779996468730920960/2020-11-21_20.13.36.png) * GameSense 2.1 Theme: -![gamesense21](https://cdn.discordapp.com/attachments/755077474861449277/770697959947239424/2020-10-27_18.17.12.png) +![gamesense21](https://cdn.discordapp.com/attachments/747111616407011389/779996509717659658/2020-11-21_20.16.09.png) * PepsiMod Theme: -![pepsimod](https://cdn.discordapp.com/attachments/755077474861449277/770698000129327124/2020-10-27_18.17.22.png) +![pepsimod](https://cdn.discordapp.com/attachments/747111616407011389/779996535366090772/2020-11-21_20.20.43.png) * GameSense 2.2 Theme: -![gamesense22](https://cdn.discordapp.com/attachments/767021200685400075/772018964414857246/unknown.png) +![gamesense22](https://cdn.discordapp.com/attachments/747111616407011389/779996442285834240/2020-11-21_19.57.25.png) * Future Theme: -![future](https://cdn.discordapp.com/attachments/755077474861449277/771799117998718986/unknown.png) +![future](https://cdn.discordapp.com/attachments/747111616407011389/779996632334073896/2020-11-21_20.25.30.png) This repostiory only includes the GameSense themes, however, since Cyber didn't want me to publish the other themes. The library has no depedencies (aside from the JRE itself), so it can be easily used for other purposes, aside from Minecraft utility mods. Thanks to Go_Hoosiers, for suggesting the name of this library. If you use this library, some attribution would be greatly appreciated. +## Features +* Ability to easily create new themes/skins. +* Overlapping Panels. +* Smooth animations and scrolling. +* Ability to have HUD components in panels. + ## Implementation in Minecraft clients A jar of this library is available in the Maven repository at https://lukflug.github.io/maven/ as `com.lukflug.panelstudio`. -To use this library in your Minecraft clients, you have to do following things: -* Implement the `Interface` interface. -* Implement the `ColorScheme` interface. +### ClickGUI +To use the ClickGUI, following interfaces need to be implemented +* `Interface` +* `ColorScheme` +In addition: * Use one of the supplied Themes or implement one yourself, to have a different look from GameSense (see `ClearTheme` and `GameSenseTheme` for reference). -* Populate the `ClickGUI` object with the desired components (probably requires marking your settings objects with the interfaces in the `settings` package). -* It is recommended to have a class extending Minecraft's `GuiScreen` and implementing `Interface`, that has `ClickGUI` as a field and populates it in the constructor. -* For reference, consult the [javadoc](https://lukflug.github.io/javadoc/panelstudio/0.0.3/) and see the implementation in GameSense. +* Populate the `ClickGUI` object with the desired components (i.e. adding a `DraggableContainer` for each category, adding a `CollapsibleContainer` for each module, and adding a settings component for each setting, this probably requires marking your settings objects with the interfaces in the `settings` package). +* It is recommended to have a class extending Minecraft's `GuiScreen` and implementing `Interface`, that has `ClickGUI` as a field, which gets populates in the constructor. +* For reference, consult the [javadoc](https://lukflug.github.io/javadoc/panelstudio/0.1.0/) and see the implementation in GameSense. +* Some custom classes may need to be created, for specific behaviour. + +### HUD +Use `HUDClickGUI` instead of `ClickGUI`. Requries calling rendering functions even when the ClickGUI is closed, in order to render the HUD panels. HUD componets have to be `FixedComponent` (use `HUDComponent` as base class) and have to be added to the `HUDClickGUI` via a `HUDPanel`. This will make the HUD component a draggable panel when the ClickGUI is open. PanelStuudio provides `TabGUI` as a stock HUD component. The `TabGUI` requires passing key events when the ClickGUI is closed and has to be populated with categories and modules. ## Use in Gradle Add following to your `build.gradle`: @@ -37,7 +49,7 @@ repositories { } dependencies { - compile("com.lukflug:panelstudio:0.0.3") + compile("com.lukflug:panelstudio:0.1.0") } shadowJar { diff --git a/build.gradle b/build.gradle index 7d8eeaf..5ec7c31 100644 --- a/build.gradle +++ b/build.gradle @@ -4,5 +4,5 @@ plugins { allprojects { group = 'com.lukflug' - version = '0.0.3' + version = '0.1.0' } diff --git a/pom.xml b/pom.xml index c6e0169..d4e698f 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.lukflug panelstudio - 0.0.3 + 0.1.0 PanelStudio A simple yet flexible library to create ClickGUIs designed for use in Minecraft utility mods. diff --git a/src/main/java/com/lukflug/panelstudio/Animation.java b/src/main/java/com/lukflug/panelstudio/Animation.java new file mode 100644 index 0000000..98f002f --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/Animation.java @@ -0,0 +1,65 @@ +package com.lukflug.panelstudio; + +/** + * Class representing an animation. + * @author lukflug + */ +public abstract class Animation { + /** + * Current value. + */ + protected double value; + /** + * Past value. + */ + protected double lastValue; + /** + * Time of last value transition. + */ + protected long lastTime=System.currentTimeMillis(); + + /** + * Set a value immediately, without an transition animation. + * @param value the new value + */ + public void initValue(double value) { + this.value=value; + lastValue=value; + } + + /** + * The the current value. + * @return an interpolated value between {@link #value} and {@link #lastValue} depending on the current time + */ + public double getValue() { + if (getSpeed()==0) return value; + double weight=(System.currentTimeMillis()-lastTime)/(double)getSpeed(); + if (weight>=1) return value; + else if (weight<=0) return lastValue; + return value*weight+lastValue*(1-weight); + } + + /** + * Get the target value. + * @return the current {@link #value} + */ + public double getTarget() { + return value; + } + + /** + * Set the value, with a transition between the old and new value. + * @param value the new value + */ + public void setValue(double value) { + lastValue=getValue(); + this.value=value; + lastTime=System.currentTimeMillis(); + } + + /** + * Used to obtain the animation speed. + * @return time a transition should take in milliseconds + */ + protected abstract int getSpeed(); +} diff --git a/src/main/java/com/lukflug/panelstudio/ClickGUI.java b/src/main/java/com/lukflug/panelstudio/ClickGUI.java index c04a117..bbd60d8 100644 --- a/src/main/java/com/lukflug/panelstudio/ClickGUI.java +++ b/src/main/java/com/lukflug/panelstudio/ClickGUI.java @@ -18,19 +18,13 @@ public class ClickGUI { * The {@link Interface} to be used by the GUI. */ protected Interface inter; - /** - * The width of the panels. - */ - protected final int width; /** * Constructor for the GUI. * @param inter the {@link Interface} to be used by the GUI - * @param width the width of the panels. */ - public ClickGUI (Interface inter, int width) { + public ClickGUI (Interface inter) { this.inter=inter; - this.width=width; components=new ArrayList(); } @@ -58,7 +52,7 @@ public void render() { FixedComponent focusComponent=null; for (int i=components.size()-1;i>=0;i--) { FixedComponent component=components.get(i); - Context context=new Context(inter,width,component.getPosition(inter),true,true); + Context context=new Context(inter,component.getWidth(inter),component.getPosition(inter),true,true); component.getHeight(context); if (context.isHovered()) { highest=i; @@ -67,7 +61,7 @@ public void render() { } for (int i=0;i=highest); + Context context=new Context(inter,component.getWidth(inter),component.getPosition(inter),true,i>=highest); component.render(context); if (context.foucsRequested()) focusComponent=component; } @@ -88,7 +82,7 @@ public void handleButton (int button) { FixedComponent focusComponent=null; for (int i=components.size()-1;i>=0;i--) { FixedComponent component=components.get(i); - Context context=new Context(inter,width,component.getPosition(inter),true,highest); + Context context=new Context(inter,component.getWidth(inter),component.getPosition(inter),true,highest); component.handleButton(context,button); if (context.isHovered()) highest=false; if (context.foucsRequested()) focusComponent=component; @@ -107,7 +101,7 @@ public void handleKey (int scancode) { boolean highest=true; FixedComponent focusComponent=null; for (FixedComponent component: components) { - Context context=new Context(inter,width,component.getPosition(inter),true,highest); + Context context=new Context(inter,component.getWidth(inter),component.getPosition(inter),true,highest); component.handleKey(context,scancode); if (context.isHovered()) highest=false; if (context.foucsRequested()) focusComponent=component; @@ -118,6 +112,25 @@ public void handleKey (int scancode) { } } + /** + * Handle the mouse wheel being scrolled + * @param diff the amount by which the wheel was moved + */ + public void handleScroll (int diff) { + boolean highest=true; + FixedComponent focusComponent=null; + for (FixedComponent component: components) { + Context context=new Context(inter,component.getWidth(inter),component.getPosition(inter),true,highest); + component.handleScroll(context,diff); + if (context.isHovered()) highest=false; + if (context.foucsRequested()) focusComponent=component; + } + if (focusComponent!=null) { + components.remove(focusComponent); + components.add(focusComponent); + } + } + /** * Handle the GUI being closed. */ @@ -125,7 +138,7 @@ public void exit() { boolean highest=true; FixedComponent focusComponent=null; for (FixedComponent component: components) { - Context context=new Context(inter,width,component.getPosition(inter),true,highest); + Context context=new Context(inter,component.getWidth(inter),component.getPosition(inter),true,highest); component.exit(context); if (context.isHovered()) highest=false; if (context.foucsRequested()) focusComponent=component; diff --git a/src/main/java/com/lukflug/panelstudio/CollapsibleContainer.java b/src/main/java/com/lukflug/panelstudio/CollapsibleContainer.java index 44280cd..eb68157 100644 --- a/src/main/java/com/lukflug/panelstudio/CollapsibleContainer.java +++ b/src/main/java/com/lukflug/panelstudio/CollapsibleContainer.java @@ -1,27 +1,56 @@ package com.lukflug.panelstudio; +import java.awt.Rectangle; + +import com.lukflug.panelstudio.settings.AnimatedToggleable; import com.lukflug.panelstudio.settings.Toggleable; import com.lukflug.panelstudio.theme.Renderer; /** - * Container that can be closed, so that its child component. + * Container that can be closed and scrolled, so that its children can be hidden. * @author lukflug */ -public class CollapsibleContainer extends Container { +public class CollapsibleContainer extends FocusableComponent implements Toggleable { + /** + * {@link Container} containing the children. + */ + protected Container container; /** * {@link Toggleable} indicating whether the container is open or closed. */ - public Toggleable open; + protected AnimatedToggleable open; + /** + * Cached combined height of children. + */ + protected int childHeight=0; + /** + * Cached container scroll height. + */ + protected int containerHeight=0; + /** + * Current scroll offset. + */ + protected int scrollPosition=0; /** * Constructor. * @param title the caption for the container * @param renderer the {@link Renderer} for the container * @param open the {@link Toggleable} for {@link #open} + * @param animation the animation for this container */ - public CollapsibleContainer (String title, Renderer renderer, Toggleable open) { + public CollapsibleContainer (String title, Renderer renderer, Toggleable open, Animation animation) { super(title,renderer); - this.open=open; + container=new Container(title,renderer); + this.open=new AnimatedToggleable(open,animation); + } + + /** + * Add a component to the container. + * @param component the component to be added + */ + public void addComponent (Component component) { + container.addComponent(component); } /** @@ -31,12 +60,20 @@ public CollapsibleContainer (String title, Renderer renderer, Toggleable open) { public void render (Context context) { getHeight(context); renderer.renderBackground(context,hasFocus(context)); - context.setHeight(renderer.getHeight()); - renderer.renderTitle(context,title,hasFocus(context),isActive(),open.isOn()); - if (open.isOn()) { - super.render(context); + super.render(context); + renderer.renderTitle(context,title,hasFocus(context),isActive(),open.getValue()!=0); + if (open.getValue()!=0) { + // Pre-calculate clipping rectangle + Context subContext=new Context(context,0,getContainerOffset(),hasFocus(context),open.getValue()==1); + container.getHeight(subContext); + Rectangle rect=getClipRect(context,subContext.getSize().height); + if (rect!=null) context.getInterface().window(rect); + // Render component + container.render(subContext); + if (rect!=null) context.getInterface().restore(); + context.setHeight(getRenderHeight(subContext.getSize().height)); } - renderer.renderBorder(context,hasFocus(context),isActive(),open.isOn()); + renderer.renderBorder(context,hasFocus(context),isActive(),open.getValue()!=0); } /** @@ -44,11 +81,20 @@ public void render (Context context) { */ @Override public void handleButton (Context context, int button) { - if (open.isOn()) super.handleButton(context,button); - else { - context.setHeight(renderer.getHeight()); + if (open.getValue()==1) { + // Pre-calculate clipping rectangle and update focus state + Context subContext=new Context(context,0,getContainerOffset(),hasFocus(context)); + container.getHeight(subContext); + context.setHeight(getRenderHeight(subContext.getSize().height)); updateFocus(context,button); - } + // Handle button click with proper onTop masking + boolean onTop=true; + Rectangle rect=getClipRect(context,subContext.getSize().height); + if (rect!=null) onTop=rect.contains(context.getInterface().getMouse()); + subContext=new Context(context,0,getContainerOffset(),hasFocus(context),onTop); + container.handleButton(subContext,button); + context.setHeight(getRenderHeight(subContext.getSize().height)); + } else super.handleButton(context,button); if (context.isHovered() && context.getInterface().getMouse().y<=context.getPos().y+renderer.getHeight() && button==Interface.RBUTTON && context.getInterface().getButton(Interface.RBUTTON)) { open.toggle(); } @@ -59,8 +105,28 @@ public void handleButton (Context context, int button) { */ @Override public void handleKey (Context context, int scancode) { - if (open.isOn()) super.handleKey(context,scancode); - else context.setHeight(renderer.getHeight()); + if (open.getValue()==1) { + Context subContext=new Context(context,0,getContainerOffset(),hasFocus(context)); + container.handleKey(subContext,scancode); + context.setHeight(getRenderHeight(subContext.getSize().height)); + } else super.handleKey(context,scancode); + } + + /** + * Scroll scroll bar. + */ + @Override + public void handleScroll (Context context, int diff) { + if (open.getValue()==1) { + Context subContext=new Context(context,0,getContainerOffset(),hasFocus(context)); + container.handleKey(subContext,diff); + context.setHeight(getRenderHeight(subContext.getSize().height)); + if (subContext.isHovered()) { + scrollPosition+=diff; + if (scrollPosition>childHeight-containerHeight) scrollPosition=childHeight-containerHeight; + if (scrollPosition<0) scrollPosition=0; + } + } else super.handleKey(context,diff); } /** @@ -68,8 +134,11 @@ public void handleKey (Context context, int scancode) { */ @Override public void getHeight (Context context) { - if (open.isOn()) super.getHeight(context); - else context.setHeight(renderer.getHeight()); + if (open.getValue()!=0) { + Context subContext=new Context(context,0,getContainerOffset(),hasFocus(context)); + container.getHeight(subContext); + context.setHeight(getRenderHeight(subContext.getSize().height)); + } else super.getHeight(context); } /** @@ -77,8 +146,11 @@ public void getHeight (Context context) { */ @Override public void exit (Context context) { - if (open.isOn()) super.exit(context); - else context.setHeight(renderer.getHeight()); + if (open.getValue()==1) { + Context subContext=new Context(context,0,getContainerOffset(),hasFocus(context)); + container.exit(subContext); + context.setHeight(getRenderHeight(subContext.getSize().height)); + } else super.exit(context); } /** @@ -88,4 +160,63 @@ public void exit (Context context) { protected boolean isActive() { return true; } + + /** + * Returns the vertical container offset. + * @return vertical offset + */ + protected int getContainerOffset() { + if (scrollPosition>childHeight-containerHeight) scrollPosition=childHeight-containerHeight; + if (scrollPosition<0) scrollPosition=0; + return (int)(renderer.getHeight()-scrollPosition-(1-open.getValue())*containerHeight); + } + + /** + * Get the height of the container, accounting for scrolling. + * @param childHeight the total height of the children + * @return the scroll height + */ + protected int getScrollHeight (int childHeight) { + return childHeight; + } + + /** + * Get the visible container height. + * @param childHeight the total height of the children + * @return the visible height + */ + protected int getRenderHeight (int childHeight) { + this.childHeight=childHeight; + containerHeight=getScrollHeight(childHeight); + if (scrollPosition>childHeight-containerHeight) scrollPosition=childHeight-containerHeight; + if (scrollPosition<0) scrollPosition=0; + return (int)(containerHeight*open.getValue()+renderer.getHeight()); + } + + /** + * Returns the clipping rectangle for the container. + * @param context the context for this component + * @param height the height of the container + * @return the clipping rectangle + */ + protected Rectangle getClipRect (Context context, int height) { + return new Rectangle(context.getPos().x,context.getPos().y+renderer.getHeight(),context.getSize().width,getRenderHeight(height)-renderer.getHeight()); + } + + /** + * Toggle the open state. And release focus of children if closing. + */ + @Override + public void toggle() { + open.toggle(); + if (!open.isOn()) container.releaseFocus(); + } + + /** + * Get the open state. + */ + @Override + public boolean isOn() { + return open.isOn(); + } } diff --git a/src/main/java/com/lukflug/panelstudio/Component.java b/src/main/java/com/lukflug/panelstudio/Component.java index 1196298..238aa85 100644 --- a/src/main/java/com/lukflug/panelstudio/Component.java +++ b/src/main/java/com/lukflug/panelstudio/Component.java @@ -37,6 +37,14 @@ public interface Component { */ public void handleKey (Context context, int scancode); + /** + * Should be called by the parent when the mouse wheel is scrolled. + * The current height of the component should be set by this method via {@link Context#setHeight(int)}. + * @param context the {@link Context} for the component + * @param diff the amount by which the wheel was moved + */ + public void handleScroll (Context context, int diff); + /** * Get the current height via {@link Context#setHeight(int)}. * @param context the {@link Context} for the component @@ -49,4 +57,9 @@ public interface Component { * @param context the {@link Context} for the component */ public void exit (Context context); + + /** + * Called when a parent loses focus. + */ + public void releaseFocus(); } diff --git a/src/main/java/com/lukflug/panelstudio/Container.java b/src/main/java/com/lukflug/panelstudio/Container.java index 668c97e..e7819d0 100644 --- a/src/main/java/com/lukflug/panelstudio/Container.java +++ b/src/main/java/com/lukflug/panelstudio/Container.java @@ -36,13 +36,12 @@ public void addComponent (Component component) { /** * Render the container. * Components are rendered in a column based on the height they specify via {@link Context#setHeight(int)}. - * Vertical space is reserved for the container itself at the top based on {@link Renderer#getHeight()}. * The horizontal border is defined by {@link Renderer#getBorder()}. * The vertical space between to components is defined by {@link Renderer#getOffset()}. */ @Override public void render (Context context) { - int posy=renderer.getHeight()+renderer.getOffset(); + int posy=renderer.getOffset(); for (Component component: components) { Context subContext=new Context(context,renderer.getBorder(),posy,hasFocus(context)); component.render(subContext); @@ -56,14 +55,15 @@ public void render (Context context) { */ @Override public void handleButton (Context context, int button) { - int posy=renderer.getHeight()+renderer.getOffset(); + int posy=renderer.getOffset(); + getHeight(context); + updateFocus(context,button); for (Component component: components) { Context subContext=new Context(context,renderer.getBorder(),posy,hasFocus(context)); component.handleButton(subContext,button); posy+=subContext.getSize().height+renderer.getOffset(); } context.setHeight(posy); - updateFocus(context,button); } /** @@ -71,7 +71,7 @@ public void handleButton (Context context, int button) { */ @Override public void handleKey (Context context, int scancode) { - int posy=renderer.getHeight()+renderer.getOffset(); + int posy=renderer.getOffset(); for (Component component: components) { Context subContext=new Context(context,renderer.getBorder(),posy,hasFocus(context)); component.handleKey(subContext,scancode); @@ -79,13 +79,27 @@ public void handleKey (Context context, int scancode) { } context.setHeight(posy); } + + /** + * Handle mouse wheel being scrolled. + */ + @Override + public void handleScroll (Context context, int diff) { + int posy=renderer.getOffset(); + for (Component component: components) { + Context subContext=new Context(context,renderer.getBorder(),posy,hasFocus(context)); + component.handleKey(subContext,diff); + posy+=subContext.getSize().height+renderer.getOffset(); + } + context.setHeight(posy); + } /** * Returns the total height of the container, accounting for the height of its child components. */ @Override public void getHeight (Context context) { - int posy=renderer.getHeight()+renderer.getOffset(); + int posy=renderer.getOffset(); for (Component component: components) { Context subContext=new Context(context,renderer.getBorder(),posy,hasFocus(context)); component.getHeight(subContext); @@ -99,7 +113,7 @@ public void getHeight (Context context) { */ @Override public void exit (Context context) { - int posy=renderer.getHeight()+renderer.getOffset(); + int posy=renderer.getOffset(); for (Component component: components) { Context subContext=new Context(context,renderer.getBorder(),posy,hasFocus(context)); component.exit(subContext); @@ -107,4 +121,22 @@ public void exit (Context context) { } context.setHeight(posy); } + + /** + * Reset focus state of self and children. + */ + @Override + public void releaseFocus() { + super.releaseFocus(); + for (Component component: components) { + component.releaseFocus(); + } + } + + /** + * Releases focus of children when called. + */ + protected void handleFocus (Context context, boolean focus) { + if (!focus) releaseFocus(); + } } diff --git a/src/main/java/com/lukflug/panelstudio/Context.java b/src/main/java/com/lukflug/panelstudio/Context.java index d39823a..3cd6ee7 100644 --- a/src/main/java/com/lukflug/panelstudio/Context.java +++ b/src/main/java/com/lukflug/panelstudio/Context.java @@ -55,6 +55,24 @@ public Context (Context context, int border, int offset, boolean focus) { onTop=context.onTop(); } + /** + * Constructor that should be used when a parent is calling a method by the child. + * {@link #inter} and {@link #onTop} are inherited without modification. + * @param context the context of the parent + * @param border the horizontal border (left and right) for the child + * @param offset the vertical position of the child relative to the parent's position + * @param focus focus state of the parent + * @param onTop whether component is in the front + */ + public Context (Context context, int border, int offset, boolean focus, boolean onTop) { + inter=context.getInterface(); + size=new Dimension(context.getSize().width-border*2,0); + position=new Point(context.getPos()); + position.translate(border,offset); + this.focus=context.hasFocus()&&focus; + this.onTop=context.onTop()&&onTop; + } + /** * Constructor that should be used by the root parent (i.e. {@link ClickGUI}). * @param inter the current {@link Interface} @@ -139,7 +157,7 @@ public boolean foucsRequested() { * @return set to true, if mouse is hovering and component isn't below another one */ public boolean isHovered() { - return inter.getMouse().x>=position.x && inter.getMouse().x<=position.x+size.width && inter.getMouse().y>=position.y && inter.getMouse().y<=position.y+size.height && onTop; + return new Rectangle(position,size).contains(inter.getMouse()) && onTop; } /** diff --git a/src/main/java/com/lukflug/panelstudio/DraggableContainer.java b/src/main/java/com/lukflug/panelstudio/DraggableContainer.java index f156795..6d9ee2e 100644 --- a/src/main/java/com/lukflug/panelstudio/DraggableContainer.java +++ b/src/main/java/com/lukflug/panelstudio/DraggableContainer.java @@ -22,17 +22,28 @@ public class DraggableContainer extends CollapsibleContainer implements FixedCom * Current position of the panel. */ protected Point position; + /** + * The panel width. + */ + protected int width; + /** + * Whether to allow the body to be dragged. + */ + protected boolean bodyDrag=false; /** * Constructor. * @param title caption of the container * @param renderer {@link Renderer} for the container * @param open {@link Toggleable} to indicate whether the container is open or closed + * @param animation the animation for opening and closing the container * @param position the initial position of the container + * @param width the width of the container */ - public DraggableContainer(String title, Renderer renderer, Toggleable open, Point position) { - super(title,renderer,open); + public DraggableContainer(String title, Renderer renderer, Toggleable open, Animation animation, Point position, int width) { + super(title,renderer,open,animation); this.position=position; + this.width=width; } /** @@ -40,18 +51,19 @@ public DraggableContainer(String title, Renderer renderer, Toggleable open, Poin */ @Override public void handleButton (Context context, int button) { - context.setHeight(renderer.getHeight()); - if (context.isClicked()) { - if (button==Interface.LBUTTON) { - dragging=true; - attachPoint=context.getInterface().getMouse(); - } + if (bodyDrag) super.handleButton(context, button); + else context.setHeight(renderer.getHeight()); + if (context.isClicked() && button==Interface.LBUTTON) { + dragging=true; + attachPoint=context.getInterface().getMouse(); } else if (!context.getInterface().getButton(Interface.LBUTTON) && dragging) { Point mouse=context.getInterface().getMouse(); dragging=false; - position.translate(mouse.x-attachPoint.x,mouse.y-attachPoint.y); + Point p=getPosition(context.getInterface()); + p.translate(mouse.x-attachPoint.x,mouse.y-attachPoint.y); + setPosition(context.getInterface(),p); } - super.handleButton(context, button); + if (!bodyDrag) super.handleButton(context, button); } /** @@ -75,6 +87,11 @@ public void setPosition(Interface inter, Point position) { this.position=new Point(position); } + @Override + public int getWidth (Interface inter) { + return width; + } + /** * Request focus within the GUI, if the panel gets focus. */ diff --git a/src/main/java/com/lukflug/panelstudio/FixedComponent.java b/src/main/java/com/lukflug/panelstudio/FixedComponent.java index 95f1d06..5a7dac6 100644 --- a/src/main/java/com/lukflug/panelstudio/FixedComponent.java +++ b/src/main/java/com/lukflug/panelstudio/FixedComponent.java @@ -21,4 +21,11 @@ public interface FixedComponent extends Component { * @param position new position */ public void setPosition (Interface inter, Point position); + + /** + * Get the component width. + * @param inter current interface + * @return component width + */ + public int getWidth (Interface inter); } diff --git a/src/main/java/com/lukflug/panelstudio/FocusableComponent.java b/src/main/java/com/lukflug/panelstudio/FocusableComponent.java index 15155a9..2da2a1d 100644 --- a/src/main/java/com/lukflug/panelstudio/FocusableComponent.java +++ b/src/main/java/com/lukflug/panelstudio/FocusableComponent.java @@ -71,6 +71,14 @@ public void handleButton (Context context, int button) { public void getHeight(Context context) { context.setHeight(renderer.getHeight()); } + + /** + * Set the height of this component to the height specified by {@link Renderer}. + */ + @Override + public void handleScroll (Context context, int diff) { + context.setHeight(renderer.getHeight()); + } /** * Set the height of this component to the height specified by {@link Renderer}. @@ -92,7 +100,8 @@ public boolean hasFocus (Context context) { /** * Reset focus state. */ - protected void releaseFocus() { + @Override + public void releaseFocus() { focus=false; } @@ -111,9 +120,9 @@ protected void updateFocus (Context context, int button) { } /** - * Does nothing, called when the focus state changes. + * Does nothing, called when the focus state changes due to a mouse event. * @param context the {@link Context} for the component - * @param focus the new foucs state + * @param focus the new focus state */ protected void handleFocus (Context context, boolean focus) { } diff --git a/src/main/java/com/lukflug/panelstudio/Interface.java b/src/main/java/com/lukflug/panelstudio/Interface.java index f4c2854..235297a 100644 --- a/src/main/java/com/lukflug/panelstudio/Interface.java +++ b/src/main/java/com/lukflug/panelstudio/Interface.java @@ -46,6 +46,19 @@ public interface Interface { */ public void drawString (Point pos, String s, Color c); + /** + * Get the font width of a string being rendered by {@link #drawString(Point, String, Color)} + * @param s the string to be considered + * @return the font width + */ + public int getFontWidth (String s); + + /** + * Get height of font rendered by {@link #drawString(Point, String, Color)} + * @return the font height + */ + public int getFontHeight(); + /** * Draw a triangle on the screen. * The color of the triangle should ideally be smoothly interpolated. @@ -116,16 +129,16 @@ public interface Interface { public void drawImage (Rectangle r, int rotation, boolean parity, int image); /** - * Clip all rendering on screen outside the specified rectangle. + * Clip all rendering on screen outside the intersection of the specified rectangle and the current clipping rectangle. * May only be called in a GUI rendering method. - * The calling method should disable clipping by calling {@link #restore()} after rendering. + * The calling method should restore clipping by calling {@link #restore()} after rendering. * @param r the clipping rectangle * @see #restore() */ public void window (Rectangle r); /** - * Disable the clipping. + * Restore the clipping to the previous state. * @see #window(Rectangle) */ public void restore(); diff --git a/src/main/java/com/lukflug/panelstudio/Slider.java b/src/main/java/com/lukflug/panelstudio/Slider.java index 5b51e6b..325c9c8 100644 --- a/src/main/java/com/lukflug/panelstudio/Slider.java +++ b/src/main/java/com/lukflug/panelstudio/Slider.java @@ -27,7 +27,7 @@ public Slider(String title, Renderer renderer) { public void render (Context context) { super.render(context); if (context.isClicked()) { - double value=(context.getInterface().getMouse().x-context.getPos().x)/(double)context.getSize().width; + double value=(context.getInterface().getMouse().x-context.getPos().x)/(double)(context.getSize().width-1); if (value<0) value=0; else if (value>1) value=1; setValue(value); diff --git a/src/main/java/com/lukflug/panelstudio/hud/HUDClickGUI.java b/src/main/java/com/lukflug/panelstudio/hud/HUDClickGUI.java new file mode 100644 index 0000000..7a61605 --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/hud/HUDClickGUI.java @@ -0,0 +1,95 @@ +package com.lukflug.panelstudio.hud; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.lukflug.panelstudio.ClickGUI; +import com.lukflug.panelstudio.FixedComponent; +import com.lukflug.panelstudio.Interface; +import com.lukflug.panelstudio.settings.Toggleable; + +/** + * ClickGUI that only renders HUD components when closed. + * @author lukflug + */ +public class HUDClickGUI extends ClickGUI implements Toggleable { + /** + * List of all components. + */ + protected List allComponents; + /** + * List of HUD components. + */ + protected Set hudComponents; + /** + * + */ + protected boolean guiOpen=false; + + /** + * Constructor. + * @param inter the interface for the ClickGUI + */ + public HUDClickGUI(Interface inter) { + super(inter); + allComponents=new ArrayList(); + hudComponents=new HashSet(); + } + + /** + * Returns all components. + */ + @Override + public List getComponents() { + return allComponents; + } + + /** + * Add component to {@link #allComponents} instead of the {@link ClickGUI} list. + */ + @Override + public void addComponent (FixedComponent component) { + allComponents.add(component); + if (guiOpen) super.addComponent(component); + } + + /** + * Add component to {@link #allComponents} and {@link #hudComponents}. + * @param component the new HUD component + */ + public void addHUDComponent (FixedComponent component) { + hudComponents.add(component); + addComponent(component); + if (!guiOpen) super.addComponent(component); + } + + /** + * Toggles GUI open state and chooses right components to render. + */ + @Override + public void toggle() { + guiOpen=!guiOpen; + if (guiOpen) components=allComponents; + else selectHUDComponents(); + } + + /** + * Returns whether GUI is open. + */ + @Override + public boolean isOn() { + return guiOpen; + } + + /** + * Add only HUD components to component list. + */ + protected void selectHUDComponents() { + components=new ArrayList(); + for (FixedComponent component: allComponents) { + if (hudComponents.contains(component)) components.add(component); + } + } +} diff --git a/src/main/java/com/lukflug/panelstudio/hud/HUDComponent.java b/src/main/java/com/lukflug/panelstudio/hud/HUDComponent.java new file mode 100644 index 0000000..5ee1d05 --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/hud/HUDComponent.java @@ -0,0 +1,101 @@ +package com.lukflug.panelstudio.hud; + +import java.awt.Point; + +import com.lukflug.panelstudio.Context; +import com.lukflug.panelstudio.FixedComponent; +import com.lukflug.panelstudio.Interface; +import com.lukflug.panelstudio.theme.Renderer; + +/** + * Base class for HUD elements. + * @author lukflug + */ +public abstract class HUDComponent implements FixedComponent { + /** + * The caption of this component. + */ + protected String title; + /** + * The {@link Renderer} for this component. + */ + protected Renderer renderer; + /** + * Current position; + */ + protected Point position; + + public HUDComponent (String title, Renderer renderer, Point position) { + this.title=title; + this.renderer=renderer; + this.position=position; + } + + @Override + public String getTitle() { + return title; + } + + /** + * Set component height. + */ + @Override + public void render(Context context) { + getHeight(context); + } + + /** + * Do nothing. + */ + @Override + public void handleButton(Context context, int button) { + getHeight(context); + } + + /** + * Do nothing. + */ + @Override + public void handleKey(Context context, int scancode) { + getHeight(context); + } + + /** + * Do nothing. + */ + @Override + public void handleScroll(Context context, int diff) { + getHeight(context); + } + + /** + * Do nothing. + */ + @Override + public void exit(Context context) { + getHeight(context); + } + + /** + * Do nothing. + */ + @Override + public void releaseFocus() { + } + + /** + * Get the component position. + */ + @Override + public Point getPosition(Interface inter) { + return new Point(position); + } + + /** + * Set the component position. + */ + @Override + public void setPosition(Interface inter, Point position) { + this.position=position; + } +} diff --git a/src/main/java/com/lukflug/panelstudio/hud/HUDPanel.java b/src/main/java/com/lukflug/panelstudio/hud/HUDPanel.java new file mode 100644 index 0000000..4ae420a --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/hud/HUDPanel.java @@ -0,0 +1,227 @@ +package com.lukflug.panelstudio.hud; + +import java.awt.Color; +import java.awt.Point; +import java.awt.Rectangle; + +import com.lukflug.panelstudio.Animation; +import com.lukflug.panelstudio.Context; +import com.lukflug.panelstudio.DraggableContainer; +import com.lukflug.panelstudio.FixedComponent; +import com.lukflug.panelstudio.Interface; +import com.lukflug.panelstudio.settings.Toggleable; +import com.lukflug.panelstudio.theme.ColorScheme; +import com.lukflug.panelstudio.theme.Renderer; + +/** + * Panel holding an HUD component. + * @author lukflug + */ +public class HUDPanel extends DraggableContainer { + /** + * Whether GUI is open. + */ + protected Toggleable guiOpen; + /** + * The HUD component. + */ + protected FixedComponent component; + + /** + * Constructor. + * @param component the component + * @param renderer the renderer for this container + * @param open toggleable indicating whether the container is open or closed + * @param animation the animation for opening and closing this container + * @param guiOpen whether to accept input and render container itself or not + * @param minBorder the minimum border for the container + */ + public HUDPanel(FixedComponent component, Renderer renderer, Toggleable open, Animation animation, Toggleable guiOpen, int minBorder) { + super(component.getTitle(),new HUDRenderer(renderer,guiOpen,minBorder),open,animation,new Point(0,0),0); + addComponent(component); + this.guiOpen=guiOpen; + this.component=component; + bodyDrag=true; + } + + /** + * Mask out input, if GUI is turned off. + */ + @Override + public void handleButton (Context context, int button) { + if (guiOpen.isOn()) super.handleButton(context,button); + } + + /** + * Mask out input, if GUI is turned off. + */ + @Override + public void handleScroll (Context context, int diff) { + if (guiOpen.isOn()) super.handleScroll(context,diff); + } + + /** + * Gets position from child component. + */ + @Override + public Point getPosition (Interface inter) { + position=component.getPosition(inter); + position.translate(0,-renderer.getHeight()-renderer.getOffset()); + return super.getPosition(inter); + } + + /** + * Sets position of child component. + */ + @Override + public void setPosition (Interface inter, Point position) { + component.setPosition(inter,new Point(position.x,position.y+renderer.getHeight()+renderer.getOffset())); + } + + /** + * Get the child component width. + */ + @Override + public int getWidth (Interface inter) { + return component.getWidth(inter)+renderer.getBorder()*2; + } + + /** + * Disable clipping, if container fully open. + */ + @Override + protected Rectangle getClipRect (Context context, int height) { + if (open.getValue()!=1) return super.getClipRect(context,height); + else return null; + } + + + /** + * Proxy for a {@link Renderer}, doesn't display container, when GUI is off. + * @author lukflug + */ + protected static class HUDRenderer implements Renderer { + /** + * Base renderer. + */ + Renderer renderer; + /** + * Whether GUI is open. + */ + protected Toggleable guiOpen; + /** + * Minimum border. + */ + protected int minBorder; + + /** + * Constructor. + * @param renderer the base renderer + * @param guiOpen whether to accept input and render container itself or not + * @param minBorder the minimum border for the container + */ + public HUDRenderer (Renderer renderer, Toggleable guiOpen, int minBorder) { + this.renderer=renderer; + this.guiOpen=guiOpen; + this.minBorder=minBorder; + } + + /** + * Returns the height defined by the base renderer. + */ + @Override + public int getHeight() { + return renderer.getHeight(); + } + + /** + * Returns the offset defined by the base renderer, if it is larger than {@link #minBorder}. + * Otherwise it will return {@link #minBorder}. + */ + @Override + public int getOffset() { + return Math.max(renderer.getOffset(),minBorder); + } + + /** + * Returns the border defined by the base renderer, if it is larger than {@link #minBorder}. + * Otherwise it will return {@link #minBorder}. + */ + @Override + public int getBorder() { + return Math.max(renderer.getBorder(),minBorder); + } + + @Override + public void renderTitle(Context context, String text, boolean focus) { + if (guiOpen.isOn()) renderer.renderTitle(context,text,focus); + } + + @Override + public void renderTitle(Context context, String text, boolean focus, boolean active) { + if (guiOpen.isOn()) renderer.renderTitle(context,text,focus,active); + } + + @Override + public void renderTitle(Context context, String text, boolean focus, boolean active, boolean open) { + if (guiOpen.isOn()) renderer.renderTitle(context,text,focus,open); + } + + @Override + public void renderRect(Context context, String text, boolean focus, boolean active, Rectangle rectangle, boolean overlay) { + if (guiOpen.isOn()) renderer.renderRect(context,text,focus,active,rectangle,overlay); + } + + @Override + public void renderBackground(Context context, boolean focus) { + if (guiOpen.isOn()) renderer.renderBackground(context,focus); + } + + @Override + public void renderBorder(Context context, boolean focus, boolean active, boolean open) { + if (guiOpen.isOn()) renderer.renderBorder(context,focus,active,open); + } + + /** + * Returns invisible color, if GUI is off. + */ + @Override + public Color getMainColor(boolean focus, boolean active) { + if (guiOpen.isOn()) return renderer.getMainColor(focus,active); + else return new Color(0,0,0,0); + } + + /** + * Returns invisible color, if GUI is off. + */ + @Override + public Color getBackgroundColor(boolean focus) { + if (guiOpen.isOn()) return renderer.getBackgroundColor(focus); + else return new Color(0,0,0,0); + } + + /** + * Returns invisible color, if GUI is off. + */ + @Override + public Color getFontColor(boolean focus) { + if (guiOpen.isOn()) return renderer.getFontColor(focus); + else return new Color(0,0,0,0); + } + + @Override + public ColorScheme getDefaultColorScheme() { + return renderer.getDefaultColorScheme(); + } + + @Override + public void overrideColorScheme(ColorScheme scheme) { + renderer.overrideColorScheme(scheme); + } + + @Override + public void restoreColorScheme() { + renderer.restoreColorScheme(); + } + } +} diff --git a/src/main/java/com/lukflug/panelstudio/settings/AnimatedToggleable.java b/src/main/java/com/lukflug/panelstudio/settings/AnimatedToggleable.java new file mode 100644 index 0000000..653005f --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/settings/AnimatedToggleable.java @@ -0,0 +1,53 @@ +package com.lukflug.panelstudio.settings; + +import com.lukflug.panelstudio.Animation; + +/** + * Animation that is also a toggle. + * @author lukflug + */ +public final class AnimatedToggleable implements Toggleable { + /** + * The toggleable. + */ + private final Toggleable toggle; + /** + * The animation. + */ + private final Animation animation; + + /** + * Constructor. + * @param toggle the toggleable + * @param animation the animation + */ + public AnimatedToggleable (Toggleable toggle, Animation animation) { + this.toggle=toggle; + this.animation=animation; + if (toggle.isOn()) animation.initValue(1); + else animation.initValue(0); + } + + /** + * Toggle the toggle. + */ + @Override + public void toggle() { + toggle.toggle(); + if (toggle.isOn()) animation.setValue(1); + else animation.setValue(0); + } + + @Override + public boolean isOn() { + return toggle.isOn(); + } + + public double getValue() { + if (animation.getTarget()!=(toggle.isOn()?1:0)) { + if (toggle.isOn()) animation.setValue(1); + else animation.setValue(0); + } + return animation.getValue(); + } +} diff --git a/src/main/java/com/lukflug/panelstudio/settings/ColorComponent.java b/src/main/java/com/lukflug/panelstudio/settings/ColorComponent.java index f5fcd12..bbaa049 100644 --- a/src/main/java/com/lukflug/panelstudio/settings/ColorComponent.java +++ b/src/main/java/com/lukflug/panelstudio/settings/ColorComponent.java @@ -2,6 +2,7 @@ import java.awt.Color; +import com.lukflug.panelstudio.Animation; import com.lukflug.panelstudio.CollapsibleContainer; import com.lukflug.panelstudio.Context; import com.lukflug.panelstudio.FocusableComponent; @@ -40,14 +41,15 @@ public class ColorComponent extends CollapsibleContainer { * Constructor. * @param title the name of the setting * @param renderer the renderer for the color setting container + * @param animation the animation for opening and closing * @param componentRenderer the renderer for the children of the container * @param setting the setting in question * @param alpha whether to render an alpha slider * @param rainbow whether to render a rainbow slider * @param colorModel {@link Toggleable} indicating whether to use RGB (false) or HSB (true) */ - public ColorComponent(String title, Renderer renderer, Renderer componentRenderer, ColorSetting setting, boolean alpha, boolean rainbow, Toggleable colorModel) { - super(title,renderer,new SimpleToggleable(false)); + public ColorComponent(String title, Renderer renderer, Animation animation, Renderer componentRenderer, ColorSetting setting, boolean alpha, boolean rainbow, Toggleable colorModel) { + super(title,renderer,new SimpleToggleable(false),animation); this.setting=setting; this.alpha=alpha; this.rainbow=rainbow; @@ -150,14 +152,14 @@ protected double getValue() { if (colorModel.isOn()) return Color.RGBtoHSB(c.getRed(),c.getGreen(),c.getBlue(),null)[value]; switch (value) { case 0: - return c.getRed()/255.0f; + return c.getRed()/255.0; case 1: - return c.getGreen()/255.0f; + return c.getGreen()/255.0; case 2: - return c.getBlue()/255.0f; + return c.getBlue()/255.0; } } - return c.getAlpha()/255.0f; + return c.getAlpha()/255.0; } /** diff --git a/src/main/java/com/lukflug/panelstudio/settings/ToggleableContainer.java b/src/main/java/com/lukflug/panelstudio/settings/ToggleableContainer.java index 72a1aa9..1f824b0 100644 --- a/src/main/java/com/lukflug/panelstudio/settings/ToggleableContainer.java +++ b/src/main/java/com/lukflug/panelstudio/settings/ToggleableContainer.java @@ -1,5 +1,6 @@ package com.lukflug.panelstudio.settings; +import com.lukflug.panelstudio.Animation; import com.lukflug.panelstudio.CollapsibleContainer; import com.lukflug.panelstudio.Context; import com.lukflug.panelstudio.Interface; @@ -20,10 +21,11 @@ public class ToggleableContainer extends CollapsibleContainer { * @param title caption of the container * @param renderer the {@link Renderer} for the container * @param open the {@link Toggleable} indicating whether the container is open or closed + * @param animation the animation for opening and closing the container * @param toggle the {@link Toggleable} to be toggled by the user */ - public ToggleableContainer(String title, Renderer renderer, Toggleable open, Toggleable toggle) { - super(title,renderer,open); + public ToggleableContainer(String title, Renderer renderer, Toggleable open, Animation animation, Toggleable toggle) { + super(title,renderer,open,animation); this.toggle=toggle; } diff --git a/src/main/java/com/lukflug/panelstudio/tabgui/DefaultRenderer.java b/src/main/java/com/lukflug/panelstudio/tabgui/DefaultRenderer.java new file mode 100644 index 0000000..8057580 --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/tabgui/DefaultRenderer.java @@ -0,0 +1,90 @@ +package com.lukflug.panelstudio.tabgui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; + +import com.lukflug.panelstudio.Context; +import com.lukflug.panelstudio.theme.ColorScheme; + +/** + * Standard TabGUI look. + * @author lukflug + */ +public class DefaultRenderer implements TabGUIRenderer { + protected ColorScheme scheme; + protected int height,border; + protected int up,down,left,right,enter; + + public DefaultRenderer (ColorScheme scheme, int height, int border, int up, int down, int left, int right, int enter) { + this.scheme=scheme; + this.border=border; + this.height=height; + this.up=up; + this.down=down; + this.left=left; + this.right=right; + this.enter=enter; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public int getBorder() { + return border; + } + + @Override + public void renderBackground(Context context, int offset, int height) { + Color bgcolor=scheme.getBackgroundColor(); + bgcolor=new Color(bgcolor.getRed(),bgcolor.getGreen(),bgcolor.getBlue(),scheme.getOpacity()); + Color border=scheme.getOutlineColor(); + Color active=scheme.getActiveColor(); + context.getInterface().fillRect(context.getRect(),bgcolor,bgcolor,bgcolor,bgcolor); + context.getInterface().drawRect(context.getRect(),border,border,border,border); + Point p=context.getPos(); + p.translate(0,offset); + Rectangle rect=new Rectangle(p,new Dimension(context.getSize().width,height)); + context.getInterface().fillRect(rect,active,active,active,active); + context.getInterface().drawRect(rect,border,border,border,border); + } + + @Override + public void renderCaption(Context context, String caption, int index, int height, boolean active) { + Color color; + if (active) color=scheme.getActiveColor(); + else color=scheme.getFontColor(); + Point p=context.getPos(); + p.translate(0,index*height); + context.getInterface().drawString(p,caption,color); + } + + @Override + public ColorScheme getColorScheme() { + return scheme; + } + + @Override + public boolean isUpKey(int key) { + return key==up; + } + + @Override + public boolean isDownKey(int key) { + return key==down; + } + + @Override + public boolean isSelectKey(int key) { + return key==right || key==enter; + } + + @Override + public boolean isEscapeKey(int key) { + return key==left; + } +} diff --git a/src/main/java/com/lukflug/panelstudio/tabgui/TabGUI.java b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUI.java new file mode 100644 index 0000000..d7ce3bc --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUI.java @@ -0,0 +1,58 @@ +package com.lukflug.panelstudio.tabgui; + +import java.awt.Point; + +import com.lukflug.panelstudio.Animation; +import com.lukflug.panelstudio.FixedComponent; +import com.lukflug.panelstudio.Interface; + +/** + * A {@link TabGUIContainer} that is also a {@link FixedComponent}. + * Should be used as root element. + * @author lukflug + */ +public class TabGUI extends TabGUIContainer implements FixedComponent { + /** + * Current position of the TabGUI. + */ + protected Point position; + /** + * The witdh of the TabGUI. + */ + protected int width; + + /** + * Constructor. + * @param title caption for the TabGUI + * @param renderer the renderer for the TabGUI + * @param animation the animation for the TabGUI + * @param position the initial position for the TabGUI + * @param width the width of the TabGUI + */ + public TabGUI(String title, TabGUIRenderer renderer, Animation animation, Point position, int width) { + super(title, renderer,animation); + this.position=position; + this.width=width; + } + + /** + * Get the current position. + */ + @Override + public Point getPosition(Interface inter) { + return new Point(position); + } + + /** + * Update the position. + */ + @Override + public void setPosition(Interface inter, Point position) { + this.position=position; + } + + @Override + public int getWidth (Interface inter) { + return width; + } +} diff --git a/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIComponent.java b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIComponent.java new file mode 100644 index 0000000..7dae332 --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIComponent.java @@ -0,0 +1,21 @@ +package com.lukflug.panelstudio.tabgui; + +import com.lukflug.panelstudio.Component; + +/** + * Interface representing a part of a TabGUI. + * @author lukflug + */ +public interface TabGUIComponent extends Component { + /** + * Boolean indicating to the parent whether to render this component highlighted. + * @return whether this component is active + */ + public boolean isActive(); + + /** + * Called by parent to indicate this component has been selected by the user. + * @return whether this component is requesting focus + */ + public boolean select(); +} diff --git a/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIContainer.java b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIContainer.java new file mode 100644 index 0000000..8e7afe6 --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIContainer.java @@ -0,0 +1,179 @@ +package com.lukflug.panelstudio.tabgui; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +import com.lukflug.panelstudio.Animation; +import com.lukflug.panelstudio.Context; +import com.lukflug.panelstudio.theme.Renderer; + +/** + * Element of TabGUI. Renders a list of child components. + * @author lukflug + */ +public class TabGUIContainer implements TabGUIComponent { + /** + * The caption of this component. + */ + protected String title; + /** + * The {@link Renderer} for this component. + */ + protected TabGUIRenderer renderer; + /** + * Child components. + */ + protected List components; + /** + * Whether child component having focus. + */ + protected boolean childOpen=false; + /** + * Index of the currently selected component. + */ + protected int selected=0; + /** + * Object handling highlight animation. + */ + protected Animation selectedAnimation=null; + + /** + * Constructor. + * @param title caption of the container + * @param renderer the {@link TabGUIRenderer} for this container + * @param animation the animation for {@link #selectedAnimation}, may be null + */ + public TabGUIContainer (String title, TabGUIRenderer renderer, Animation animation) { + this.title=title; + this.renderer=renderer; + components=new ArrayList(); + if (animation!=null) { + animation.initValue(selected); + selectedAnimation=animation; + } + } + + /** + * Add a component to this container. + * @param component the new component + */ + public void addComponent (TabGUIComponent component) { + components.add(component); + } + + /** + * Returns the component caption. + */ + @Override + public String getTitle() { + return title; + } + + /** + * Render the container. + */ + @Override + public void render(Context context) { + getHeight(context); + int offset=selected*renderer.getHeight(); + if (selectedAnimation!=null) offset=(int)(selectedAnimation.getValue()*renderer.getHeight()); + renderer.renderBackground(context,offset,renderer.getHeight()); + for (int i=0;i=components.size()) selected=0; + if (selectedAnimation!=null) selectedAnimation.setValue(selected); + } else if (renderer.isSelectKey(scancode)) { + if (components.get(selected).select()) childOpen=true; + } + } else { + components.get(selected).handleKey(getSubContext(context),scancode); + } + } + + /** + * Do nothing. + */ + @Override + public void handleScroll (Context context, int diff) { + getHeight(context); + } + + /** + * Returns the container height. + */ + @Override + public void getHeight(Context context) { + context.setHeight(renderer.getHeight()*components.size()); + } + + /** + * Do nothing. + */ + @Override + public void exit(Context context) { + getHeight(context); + } + + /** + * TabGUI container is inactive by default. + */ + @Override + public boolean isActive() { + return false; + } + + /** + * Requests focus, if selected. + */ + @Override + public boolean select() { + return true; + } + + /** + * Create a sub-context. + * @param context the current context + * @return a context for a child-component + */ + protected Context getSubContext (Context context) { + Point p=context.getPos(); + p.translate(context.getSize().width+renderer.getBorder(),selected*renderer.getHeight()); + return new Context(context.getInterface(),context.getSize().width,p,context.hasFocus(),context.onTop()); + } + + /** + * Does nothing. + */ + @Override + public void releaseFocus() { + } +} diff --git a/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIItem.java b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIItem.java new file mode 100644 index 0000000..cbdac14 --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIItem.java @@ -0,0 +1,103 @@ +package com.lukflug.panelstudio.tabgui; + +import com.lukflug.panelstudio.Context; +import com.lukflug.panelstudio.settings.Toggleable; + +/** + * Component representing leaf in TabGUI hierarchy. + * @author lukflug + */ +public class TabGUIItem implements TabGUIComponent { + /** + * Caption of the component. + */ + protected String title; + /** + * Toggle indicating whether this component is active. + */ + protected Toggleable toggle; + + /** + * Constructor. + * @param title caption of the component + * @param toggle toggle for {@link #isActive()} state + */ + public TabGUIItem (String title, Toggleable toggle) { + this.title=title; + this.toggle=toggle; + } + + /** + * Returns the component caption. + */ + @Override + public String getTitle() { + return title; + } + + /** + * Does nothing. + */ + @Override + public void render(Context context) { + } + + /** + * Does nothing. + */ + @Override + public void handleButton(Context context, int button) { + } + + /** + * Does nothing. + */ + @Override + public void handleKey(Context context, int scancode) { + } + + /** + * Does nothing. + */ + @Override + public void handleScroll (Context context, int diff) { + } + + /** + * Does nothing. + */ + @Override + public void getHeight(Context context) { + } + + /** + * Does nothing. + */ + @Override + public void exit(Context context) { + } + + /** + * Returns the value of the {@link #toggle}. + */ + @Override + public boolean isActive() { + return toggle.isOn(); + } + + /** + * Toggles the {@link #toggle} and does not request focus. + */ + @Override + public boolean select() { + toggle.toggle(); + return false; + } + + /** + * Does nothing. + */ + @Override + public void releaseFocus() { + } +} diff --git a/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIRenderer.java b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIRenderer.java new file mode 100644 index 0000000..4d20b1d --- /dev/null +++ b/src/main/java/com/lukflug/panelstudio/tabgui/TabGUIRenderer.java @@ -0,0 +1,74 @@ +package com.lukflug.panelstudio.tabgui; + +import com.lukflug.panelstudio.Context; +import com.lukflug.panelstudio.theme.ColorScheme; + +/** + * An Interface to abstract the rendering of TabGUI containers. + * @author lukflug + */ +public interface TabGUIRenderer { + /** + * Get the default component height. + * @return component height + */ + public int getHeight(); + + /** + * Get the default component distance between parent and child container. + * @return component border + */ + public int getBorder(); + + /** + * Renders the background of a component. + * @param context the current context + * @param offset the vertical position of the text highlight + * @param height the height of the text highlight and of single components + */ + public void renderBackground (Context context, int offset, int height); + + /** + * Renders the caption of a child in a container. + * @param context the current context + * @param caption caption of the child in question + * @param index the index of the child in the container + * @param height the height of a single child component + * @param active whether the child component is active + */ + public void renderCaption (Context context, String caption, int index, int height, boolean active); + + /** + * Returns the default color scheme. + * @return the color scheme + */ + public ColorScheme getColorScheme(); + + /** + * Check whether key scancode is up key. + * @param key scancode in question + * @return true if key is up key + */ + public boolean isUpKey (int key); + + /** + * Check whether key scancode is down key. + * @param key scancode in question + * @return true if key is down key + */ + public boolean isDownKey (int key); + + /** + * Check whether key scancode is select key. + * @param key scancode in question + * @return true if key is select key + */ + public boolean isSelectKey (int key); + + /** + * Check whether key scancode is escape key. + * @param key scancode in question + * @return true if key is escape key + */ + public boolean isEscapeKey (int key); +}