From ea86da6b74517556f849ad77cacd6ae5182cb122 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Fri, 13 Dec 2024 19:14:16 +0100 Subject: [PATCH 01/15] Refactor BevelVerticesModifier for Improved Clarity and Workflow Separation - Split the beveling workflow into clear, modular methods for better readability. - Added `modifyWorkflow()` to encapsulate the entire beveling process sequence. - Improved edge point creation with dedicated logic in `generateEdgePointsForFaces`. - Introduced `clear()` to reset all related data structures before processing. - Updated traversal logic to ensure edge processing is more structured and efficient. - Removed redundant utility methods in favor of streamlined logic with clearer names. - Enhanced error handling with checks (e.g., null checks for meshes). - Improved the use of helper methods (`toReverseArray`) for geometry transformations. --- .../mesh/modifier/BevelVerticesModifier.java | 323 +++++++++++++----- 1 file changed, 238 insertions(+), 85 deletions(-) diff --git a/src/main/java/mesh/modifier/BevelVerticesModifier.java b/src/main/java/mesh/modifier/BevelVerticesModifier.java index fb871512..c1729b72 100644 --- a/src/main/java/mesh/modifier/BevelVerticesModifier.java +++ b/src/main/java/mesh/modifier/BevelVerticesModifier.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Vector; +import java.util.Map; import math.Vector3f; import mesh.Edge3D; @@ -11,159 +11,312 @@ import mesh.Mesh3D; import mesh.util.TraverseHelper; +/** + * This class modifies a 3D mesh by beveling its vertices. + * + *

+ * The beveling process creates new vertices along the edges of the mesh and + * generates new faces to connect these vertices, resulting in a chamfered + * effect. + *

+ * + *

+ * This implementation supports basic vertex beveling with a customizable bevel + * amount. + *

+ */ public class BevelVerticesModifier implements IMeshModifier { + /** + * The default bevel amount used if no custom amount is specified during + * instantiation. This determines the default intensity of the bevel effect + * applied to the mesh edges. + */ + private static final float DEFAULT_AMOUNT = 0.1f; + + /** + * The amount of bevel to apply to the vertices and edges of the mesh. This + * value defines how much the edges are chamfered. + */ private float amount; + /** + * The 3D mesh to be modified by this class. + */ private Mesh3D mesh; + /** + * A list of new vertices generated during the beveling process. + */ private List verticesToAdd; + /** + * A list of new faces to be added to the mesh to form the bevel effect. + */ private List facesToAdd; - private HashMap edgeToEdgePointIndex; - + /** + * A map connecting edges to their corresponding new beveled vertex indices. + * This is used to efficiently track which new vertices are associated with + * specific edges, ensuring the mesh geometry is modified correctly. + */ + private Map edgeToEdgePointIndex; + + /** + * A map that tracks unique vertices (edge points) to their corresponding + * index. This ensures that vertices are reused rather than duplicated during + * the beveling process, maintaining the mesh's geometric integrity and + * avoiding unnecessary computations. + */ + private Map vertexIndexMap; + + /** + * Default constructor with a predefined bevel amount of 0.1. + */ public BevelVerticesModifier() { - this(0.1f); + this(DEFAULT_AMOUNT); } + /** + * Constructs a new BevelVerticesModifier with a custom bevel amount. + * + * @param amount The amount to bevel. Must be positive. + * @throws IllegalArgumentException if the bevel amount is less than or equal + * to zero. + */ public BevelVerticesModifier(float amount) { + if (amount <= 0) { + throw new IllegalArgumentException("Bevel amount must be positive."); + } this.amount = amount; - verticesToAdd = new ArrayList(); - facesToAdd = new ArrayList(); - edgeToEdgePointIndex = new HashMap(); + this.verticesToAdd = new ArrayList<>(); + this.facesToAdd = new ArrayList<>(); + this.edgeToEdgePointIndex = new HashMap<>(); + this.vertexIndexMap = new HashMap<>(); } + /** + * Modifies the given mesh by applying the beveling workflow. + * + * @param mesh The 3D mesh to be modified. + * @return The modified mesh after applying the bevel. + * @throws IllegalArgumentException if the mesh is null. + */ @Override public Mesh3D modify(Mesh3D mesh) { + if (mesh == null) { + throw new IllegalArgumentException("Mesh cannot be null."); + } + modifyWorkflow(mesh); + return mesh; + } + + /** + * Runs the entire beveling workflow, including edge point generation, face + * creation, clearing old geometry, and adding new geometry. + * + * @param mesh The 3D mesh to modify during the workflow. + */ + private void modifyWorkflow(Mesh3D mesh) { clear(); setMesh(mesh); - createEdgePoints(); - createFacesForVerticesAroundOldVertex(); - removeOldFaces(); - removeOldVertices(); - addNewVertices(); - addNewFaces(); - return mesh; + generateEdgePointsForFaces(); + createFacesAroundVertices(); + removeOldFacesAndVertices(); + addNewVerticesAndFaces(); } - private void createEdgePointsOf(Face3D face) { - Vector indices = new Vector(); + /** + * Generates edge points for all faces in the mesh. + */ + private void generateEdgePointsForFaces() { + for (Face3D face : mesh.faces) { + generateEdgePointsForFace(face); + } + } + /** + * Generates edge points for a single face and maps them for further + * processing. + * + * @param face The face to process edge points for. + */ + private void generateEdgePointsForFace(Face3D face) { + int[] indices = new int[face.indices.length * 2]; for (int i = 0; i < face.indices.length; i++) { + int index = i * 2; Vector3f from = getVertexForIndexAt(face, i); Vector3f to = getVertexForIndexAt(face, i + 1); - Vector3f edgePointFrom = calculateEdgePoint(from, to); - Vector3f edgePointTo = calculateEdgePoint(to, from); - - addEdgePoint(edgePointFrom); - addEdgePoint(edgePointTo); + indices[index] = addEdgePoint(calculateEdgePoint(to, from)); + indices[index + 1] = addEdgePoint(calculateEdgePoint(from, to)); - int edgePointFromIndex = indexOf(edgePointFrom); - int edgePointToIndex = indexOf(edgePointTo); - - indices.add(edgePointToIndex); - indices.add(edgePointFromIndex); - - Edge3D edge = new Edge3D(getIndexAt(face, i), getIndexAt(face, i + 1)); - edgeToEdgePointIndex.put(edge, edgePointToIndex); + mapEdgeToEdgePointIndex(face, i, indices[index]); } - - addFace(toArray(indices)); + addFace(indices); } - private Vector3f calculateEdgePoint(Vector3f from, Vector3f to) { - return from.subtract(to).mult(amount).add(to); + /** + * Maps edge points to their corresponding indices for quick lookup. + * + * @param face The face associated with the edge. + * @param i The index of the edge in the face. + * @param index The computed index for the edge. + */ + private void mapEdgeToEdgePointIndex(Face3D face, int i, int index) { + Edge3D edge = new Edge3D(getIndexAt(face, i), getIndexAt(face, i + 1)); + edgeToEdgePointIndex.put(edge, index); } - private void createFacesForVerticesAroundOldVertex() { + /** + * Creates new faces by connecting edge points around vertices in the mesh. + */ + private void createFacesAroundVertices() { TraverseHelper helper = new TraverseHelper(mesh); for (int i = 0; i < mesh.getVertexCount(); i++) { - Edge3D outgoingEdge = helper.getOutgoing(i); - Edge3D edge = outgoingEdge; - Vector indices = new Vector(); - do { - int index = edgeToEdgePointIndex.get(edge); - indices.add(index); - edge = helper.getPairNext(edge.fromIndex, edge.toIndex); - } while (!outgoingEdge.equals(edge)); - facesToAdd.add(new Face3D(toReverseArray(indices))); + List indices = collectEdgePointsAroundVertex(helper, i); + if (!indices.isEmpty()) { + facesToAdd.add(new Face3D(toReverseArray(indices))); + } } } - private int[] toArray(Vector values) { - int[] a = new int[values.size()]; - for (int j = 0; j < a.length; j++) { - a[j] = values.get(j); - } - return a; + /** + * Collects edge points surrounding a specific vertex using traversal logic. + * + * @param helper The traversal helper instance. + * @param vertexIndex The index of the vertex to process. + * @return A list of edge indices surrounding the vertex. + */ + private List collectEdgePointsAroundVertex(TraverseHelper helper, + int vertexIndex) { + Edge3D outgoingEdge = helper.getOutgoing(vertexIndex); + Edge3D edge = outgoingEdge; + List indices = new ArrayList<>(); + + do { + int index = edgeToEdgePointIndex.get(edge); + indices.add(index); + edge = helper.getPairNext(edge.fromIndex, edge.toIndex); + } while (!outgoingEdge.equals(edge)); + + return indices; } - private int[] toReverseArray(Vector values) { - int[] a = new int[values.size()]; - for (int j = 0; j < a.length; j++) { - int index = a.length - j - 1; - a[index] = values.get(j); + /** + * Adds a new edge point to the list or retrieves the index if it already + * exists. + * + * @param edgePoint The edge point to add. + * @return The index of the edge point. + */ + private int addEdgePoint(Vector3f edgePoint) { + if (!vertexIndexMap.containsKey(edgePoint)) { + vertexIndexMap.put(edgePoint, verticesToAdd.size()); + verticesToAdd.add(edgePoint); } - return a; - } - - private void clear() { - verticesToAdd.clear(); - facesToAdd.clear(); - edgeToEdgePointIndex.clear(); + return vertexIndexMap.get(edgePoint); } - private void removeOldVertices() { - mesh.vertices.clear(); - } - - private void removeOldFaces() { + /** + * Clears out old faces and vertices from the mesh. + */ + private void removeOldFacesAndVertices() { mesh.faces.clear(); + mesh.vertices.clear(); } - private void addNewVertices() { + /** + * Adds newly computed vertices and faces to the mesh. + */ + private void addNewVerticesAndFaces() { mesh.vertices.addAll(verticesToAdd); - } - - private void addNewFaces() { mesh.faces.addAll(facesToAdd); } - private void createEdgePoints() { - for (Face3D face : mesh.faces) - createEdgePointsOf(face); - } - + /** + * Adds a new face to the list of faces to be added to the mesh. + * + * @param indices The indices defining the new face. + */ private void addFace(int[] indices) { facesToAdd.add(new Face3D(indices)); } + /** + * Calculates a new edge point based on a linear interpolation with the given + * bevel amount. + * + * @param from The starting vertex. + * @param to The ending vertex. + * @return The calculated edge point. + */ + private Vector3f calculateEdgePoint(Vector3f from, Vector3f to) { + return from.subtract(to).mult(amount).add(to); + } + + /** + * Retrieves the index of the vertex at the specified position in the face's + * indices list, with support for circular indexing by wrapping around when + * necessary. + * + * @param face The face from which to retrieve the index. Must not be null. + * @param i The position of the index to retrieve within the face's + * indices. + * @return The vertex index at the specified position, with wrapping support. + */ private int getIndexAt(Face3D face, int i) { return face.indices[i % face.indices.length]; } + /** + * Retrieves the actual 3D vertex (as a Vector3f) associated with the + * specified index in the face's indices list. This maps the index to the + * corresponding vertex in the provided mesh. + * + * @param face The face whose index to map to a vertex. Must not be null. + * @param i The index position within the face's indices. + * @return The 3D vertex (Vector3f) corresponding to the index. + */ private Vector3f getVertexForIndexAt(Face3D face, int i) { int index = getIndexAt(face, i); return mesh.getVertexAt(index); } - private void addEdgePoint(Vector3f edgePoint) { - if (!contains(edgePoint)) - verticesToAdd.add(edgePoint); - } - - private boolean contains(Vector3f v) { - return verticesToAdd.contains(v); + /** + * Reverses the order of integers in the provided list and converts it to an + * array. This is used to reverse traversal or ordering logic in certain + * geometric calculations. + * + * @param values The list of integer indices to reverse. + * @return A new integer array with the order of elements reversed compared to + * the input list. + */ + private int[] toReverseArray(List values) { + int[] a = new int[values.size()]; + for (int j = 0; j < a.length; j++) { + a[j] = values.get(values.size() - 1 - j); + } + return a; } - private int indexOf(Vector3f v) { - return verticesToAdd.indexOf(v); + /** + * Clears the old data structures related to the beveling process. + */ + private void clear() { + verticesToAdd.clear(); + facesToAdd.clear(); + edgeToEdgePointIndex.clear(); + vertexIndexMap.clear(); } + /** + * Sets the mesh to be modified during the workflow. + * + * @param mesh The mesh to work on. + */ private void setMesh(Mesh3D mesh) { this.mesh = mesh; } -} +} \ No newline at end of file From 5b8cf0d4d28ab3ebdf7c7f9633cf5d69a5bba5e5 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 07:45:39 +0100 Subject: [PATCH 02/15] Format changes. --- .../workspace/command/AbstractKeyCommand.java | 56 +++++++++---------- .../command/AbstractWorkspaceKeyCommand.java | 20 +++---- .../java/workspace/command/KeyCommand.java | 10 ++-- .../java/workspace/command/KeyCommandMap.java | 34 +++++------ .../command/ResetPanningCommand.java | 20 +++---- .../command/ShadeSmoothFlatCommand.java | 23 ++++---- .../command/ShowHideEdgesCommand.java | 18 +++--- .../command/ShowHideFaceNormalsCommand.java | 18 +++--- .../command/ShowHideGridCommand.java | 18 +++--- .../command/ShowHideSideBarCommand.java | 18 +++--- .../command/ShowHideVertexNormalsCommand.java | 19 +++---- .../command/ShowHideXAxisCommand.java | 18 +++--- .../command/ShowHideYAxisCommand.java | 18 +++--- .../command/ShowHideZAxisCommand.java | 18 +++--- .../workspace/command/WireframeCommand.java | 18 +++--- .../ui/border/{IBorder.java => Border.java} | 0 16 files changed, 162 insertions(+), 164 deletions(-) rename src/main/java/workspace/ui/border/{IBorder.java => Border.java} (100%) diff --git a/src/main/java/workspace/command/AbstractKeyCommand.java b/src/main/java/workspace/command/AbstractKeyCommand.java index 6c31d7ef..f422604a 100644 --- a/src/main/java/workspace/command/AbstractKeyCommand.java +++ b/src/main/java/workspace/command/AbstractKeyCommand.java @@ -2,43 +2,43 @@ public abstract class AbstractKeyCommand implements KeyCommand { - private char key; + private char key; - private boolean enabled; + private boolean enabled; - private String name; + private String name; - public AbstractKeyCommand() { - setEnabled(true); - } + public AbstractKeyCommand() { + setEnabled(true); + } - @Override - public abstract void execute(); + @Override + public abstract void execute(); - @Override - public String getName() { - return name; - } + @Override + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - @Override - public char getKey() { - return key; - } + @Override + public char getKey() { + return key; + } - public void setKey(char key) { - this.key = key; - } + public void setKey(char key) { + this.key = key; + } - public boolean isEnabled() { - return enabled; - } + public boolean isEnabled() { + return enabled; + } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } diff --git a/src/main/java/workspace/command/AbstractWorkspaceKeyCommand.java b/src/main/java/workspace/command/AbstractWorkspaceKeyCommand.java index 596f373a..af57c3b1 100644 --- a/src/main/java/workspace/command/AbstractWorkspaceKeyCommand.java +++ b/src/main/java/workspace/command/AbstractWorkspaceKeyCommand.java @@ -4,18 +4,18 @@ public abstract class AbstractWorkspaceKeyCommand extends AbstractKeyCommand { - private WorkspaceModel model; + private WorkspaceModel model; - public AbstractWorkspaceKeyCommand(WorkspaceModel model) { - this.model = model; - } + public AbstractWorkspaceKeyCommand(WorkspaceModel model) { + this.model = model; + } - public WorkspaceModel getModel() { - return model; - } + public WorkspaceModel getModel() { + return model; + } - public void setModel(WorkspaceModel model) { - this.model = model; - } + public void setModel(WorkspaceModel model) { + this.model = model; + } } diff --git a/src/main/java/workspace/command/KeyCommand.java b/src/main/java/workspace/command/KeyCommand.java index 533fef12..9ee728d4 100644 --- a/src/main/java/workspace/command/KeyCommand.java +++ b/src/main/java/workspace/command/KeyCommand.java @@ -2,14 +2,14 @@ public interface KeyCommand { - void execute(); + void execute(); - String getName(); + String getName(); - char getKey(); + char getKey(); - void setEnabled(boolean enabled); + void setEnabled(boolean enabled); - boolean isEnabled(); + boolean isEnabled(); } diff --git a/src/main/java/workspace/command/KeyCommandMap.java b/src/main/java/workspace/command/KeyCommandMap.java index 3d1e289e..2f72edaf 100644 --- a/src/main/java/workspace/command/KeyCommandMap.java +++ b/src/main/java/workspace/command/KeyCommandMap.java @@ -4,28 +4,28 @@ public class KeyCommandMap { - private HashMap commands; + private HashMap commands; - public KeyCommandMap() { - this.commands = new HashMap(); - } + public KeyCommandMap() { + this.commands = new HashMap(); + } - public void register(KeyCommand command) { - commands.put(command.getKey(), command); - } + public void register(KeyCommand command) { + commands.put(command.getKey(), command); + } - public void execute(char key) { - if (!commands.containsKey(key)) - return; + public void execute(char key) { + if (!commands.containsKey(key)) + return; - KeyCommand command = commands.get(key); + KeyCommand command = commands.get(key); - if (command.isEnabled()) - command.execute(); - } + if (command.isEnabled()) + command.execute(); + } - public KeyCommand getCommand(char key) { - return commands.get(key); - } + public KeyCommand getCommand(char key) { + return commands.get(key); + } } diff --git a/src/main/java/workspace/command/ResetPanningCommand.java b/src/main/java/workspace/command/ResetPanningCommand.java index c418d07c..936ab57a 100644 --- a/src/main/java/workspace/command/ResetPanningCommand.java +++ b/src/main/java/workspace/command/ResetPanningCommand.java @@ -4,16 +4,16 @@ public class ResetPanningCommand extends AbstractWorkspaceKeyCommand { - public ResetPanningCommand(WorkspaceModel model) { - super(model); - setName("Reset Panning"); - setKey('c'); - } + public ResetPanningCommand(WorkspaceModel model) { + super(model); + setName("Reset Panning"); + setKey('c'); + } - @Override - public void execute() { - getModel().setPanningX(0); - getModel().setPanningY(0); - } + @Override + public void execute() { + getModel().setPanningX(0); + getModel().setPanningY(0); + } } diff --git a/src/main/java/workspace/command/ShadeSmoothFlatCommand.java b/src/main/java/workspace/command/ShadeSmoothFlatCommand.java index 5b68218b..47e0cba8 100644 --- a/src/main/java/workspace/command/ShadeSmoothFlatCommand.java +++ b/src/main/java/workspace/command/ShadeSmoothFlatCommand.java @@ -5,18 +5,17 @@ public class ShadeSmoothFlatCommand extends AbstractWorkspaceKeyCommand { - public ShadeSmoothFlatCommand(WorkspaceModel model) { - super(model); - setName("Shade Smooth"); - setKey('s'); - } + public ShadeSmoothFlatCommand(WorkspaceModel model) { + super(model); + setName("Shade Smooth"); + setKey('s'); + } - @Override - public void execute() { - getModel().setShading( - getModel().getShading() == Shading.SMOOTH ? Shading.FLAT - : Shading.SMOOTH - ); - } + @Override + public void execute() { + getModel() + .setShading(getModel().getShading() == Shading.SMOOTH ? Shading.FLAT + : Shading.SMOOTH); + } } diff --git a/src/main/java/workspace/command/ShowHideEdgesCommand.java b/src/main/java/workspace/command/ShowHideEdgesCommand.java index b4834682..bdbaf0dd 100644 --- a/src/main/java/workspace/command/ShowHideEdgesCommand.java +++ b/src/main/java/workspace/command/ShowHideEdgesCommand.java @@ -4,15 +4,15 @@ public class ShowHideEdgesCommand extends AbstractWorkspaceKeyCommand { - public ShowHideEdgesCommand(WorkspaceModel model) { - super(model); - setName("Edges"); - setKey('e'); - } + public ShowHideEdgesCommand(WorkspaceModel model) { + super(model); + setName("Edges"); + setKey('e'); + } - @Override - public void execute() { - getModel().setEdgesVisible(!getModel().isEdgesVisible()); - } + @Override + public void execute() { + getModel().setEdgesVisible(!getModel().isEdgesVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideFaceNormalsCommand.java b/src/main/java/workspace/command/ShowHideFaceNormalsCommand.java index ddb92efb..dc334b83 100644 --- a/src/main/java/workspace/command/ShowHideFaceNormalsCommand.java +++ b/src/main/java/workspace/command/ShowHideFaceNormalsCommand.java @@ -4,15 +4,15 @@ public class ShowHideFaceNormalsCommand extends AbstractWorkspaceKeyCommand { - public ShowHideFaceNormalsCommand(WorkspaceModel model) { - super(model); - setName("Face Normals"); - setKey('n'); - } + public ShowHideFaceNormalsCommand(WorkspaceModel model) { + super(model); + setName("Face Normals"); + setKey('n'); + } - @Override - public void execute() { - getModel().setFaceNormalsVisible(!getModel().isFaceNormalsVisible()); - } + @Override + public void execute() { + getModel().setFaceNormalsVisible(!getModel().isFaceNormalsVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideGridCommand.java b/src/main/java/workspace/command/ShowHideGridCommand.java index 9c3e2307..d8f88b7a 100644 --- a/src/main/java/workspace/command/ShowHideGridCommand.java +++ b/src/main/java/workspace/command/ShowHideGridCommand.java @@ -4,15 +4,15 @@ public class ShowHideGridCommand extends AbstractWorkspaceKeyCommand { - public ShowHideGridCommand(WorkspaceModel model) { - super(model); - setName("Grid"); - setKey('g'); - } + public ShowHideGridCommand(WorkspaceModel model) { + super(model); + setName("Grid"); + setKey('g'); + } - @Override - public void execute() { - getModel().setGridVisible(!getModel().isGridVisible()); - } + @Override + public void execute() { + getModel().setGridVisible(!getModel().isGridVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideSideBarCommand.java b/src/main/java/workspace/command/ShowHideSideBarCommand.java index b81a1095..6fa28593 100644 --- a/src/main/java/workspace/command/ShowHideSideBarCommand.java +++ b/src/main/java/workspace/command/ShowHideSideBarCommand.java @@ -4,15 +4,15 @@ public class ShowHideSideBarCommand extends AbstractWorkspaceKeyCommand { - public ShowHideSideBarCommand(WorkspaceModel model) { - super(model); - setName("Sidebar"); - setKey('y'); - } + public ShowHideSideBarCommand(WorkspaceModel model) { + super(model); + setName("Sidebar"); + setKey('y'); + } - @Override - public void execute() { - getModel().setUiVisible(!getModel().isUiVisible()); - } + @Override + public void execute() { + getModel().setUiVisible(!getModel().isUiVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideVertexNormalsCommand.java b/src/main/java/workspace/command/ShowHideVertexNormalsCommand.java index 72243046..4c7d5fb2 100644 --- a/src/main/java/workspace/command/ShowHideVertexNormalsCommand.java +++ b/src/main/java/workspace/command/ShowHideVertexNormalsCommand.java @@ -4,16 +4,15 @@ public class ShowHideVertexNormalsCommand extends AbstractWorkspaceKeyCommand { - public ShowHideVertexNormalsCommand(WorkspaceModel model) { - super(model); - setName("Vertex Normals"); - setKey('v'); - } + public ShowHideVertexNormalsCommand(WorkspaceModel model) { + super(model); + setName("Vertex Normals"); + setKey('v'); + } - @Override - public void execute() { - getModel() - .setVertexNormalsVisible(!getModel().isVertexNormalsVisible()); - } + @Override + public void execute() { + getModel().setVertexNormalsVisible(!getModel().isVertexNormalsVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideXAxisCommand.java b/src/main/java/workspace/command/ShowHideXAxisCommand.java index a5855fe9..43a06732 100644 --- a/src/main/java/workspace/command/ShowHideXAxisCommand.java +++ b/src/main/java/workspace/command/ShowHideXAxisCommand.java @@ -4,15 +4,15 @@ public class ShowHideXAxisCommand extends AbstractWorkspaceKeyCommand { - public ShowHideXAxisCommand(WorkspaceModel model) { - super(model); - setName("X-Axis"); - setKey('1'); - } + public ShowHideXAxisCommand(WorkspaceModel model) { + super(model); + setName("X-Axis"); + setKey('1'); + } - @Override - public void execute() { - getModel().setxAxisVisible(!getModel().isxAxisVisible()); - } + @Override + public void execute() { + getModel().setxAxisVisible(!getModel().isxAxisVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideYAxisCommand.java b/src/main/java/workspace/command/ShowHideYAxisCommand.java index e74d3b39..78df6d31 100644 --- a/src/main/java/workspace/command/ShowHideYAxisCommand.java +++ b/src/main/java/workspace/command/ShowHideYAxisCommand.java @@ -4,15 +4,15 @@ public class ShowHideYAxisCommand extends AbstractWorkspaceKeyCommand { - public ShowHideYAxisCommand(WorkspaceModel model) { - super(model); - setName("Y-Axis"); - setKey('2'); - } + public ShowHideYAxisCommand(WorkspaceModel model) { + super(model); + setName("Y-Axis"); + setKey('2'); + } - @Override - public void execute() { - getModel().setyAxisVisible(!getModel().isyAxisVisible()); - } + @Override + public void execute() { + getModel().setyAxisVisible(!getModel().isyAxisVisible()); + } } diff --git a/src/main/java/workspace/command/ShowHideZAxisCommand.java b/src/main/java/workspace/command/ShowHideZAxisCommand.java index 9e450c54..62c1b97b 100644 --- a/src/main/java/workspace/command/ShowHideZAxisCommand.java +++ b/src/main/java/workspace/command/ShowHideZAxisCommand.java @@ -4,15 +4,15 @@ public class ShowHideZAxisCommand extends AbstractWorkspaceKeyCommand { - public ShowHideZAxisCommand(WorkspaceModel model) { - super(model); - setName("Z-Axis"); - setKey('3'); - } + public ShowHideZAxisCommand(WorkspaceModel model) { + super(model); + setName("Z-Axis"); + setKey('3'); + } - @Override - public void execute() { - getModel().setzAxisVisible(!getModel().iszAxisVisible()); - } + @Override + public void execute() { + getModel().setzAxisVisible(!getModel().iszAxisVisible()); + } } diff --git a/src/main/java/workspace/command/WireframeCommand.java b/src/main/java/workspace/command/WireframeCommand.java index 92b38d95..d2442ff4 100644 --- a/src/main/java/workspace/command/WireframeCommand.java +++ b/src/main/java/workspace/command/WireframeCommand.java @@ -4,15 +4,15 @@ public class WireframeCommand extends AbstractWorkspaceKeyCommand { - public WireframeCommand(WorkspaceModel model) { - super(model); - setName("Wireframe"); - setKey('z'); - } + public WireframeCommand(WorkspaceModel model) { + super(model); + setName("Wireframe"); + setKey('z'); + } - @Override - public void execute() { - getModel().setWireframe(!getModel().isWireframe()); - } + @Override + public void execute() { + getModel().setWireframe(!getModel().isWireframe()); + } } diff --git a/src/main/java/workspace/ui/border/IBorder.java b/src/main/java/workspace/ui/border/Border.java similarity index 100% rename from src/main/java/workspace/ui/border/IBorder.java rename to src/main/java/workspace/ui/border/Border.java From 9775ed1e3fb78e638a6591ae045fbd623978e54a Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 07:55:01 +0100 Subject: [PATCH 03/15] Format changes. --- .../java/workspace/WorkspaceSideBarUi.java | 576 +++++++++--------- 1 file changed, 286 insertions(+), 290 deletions(-) diff --git a/src/main/java/workspace/WorkspaceSideBarUi.java b/src/main/java/workspace/WorkspaceSideBarUi.java index acf581a1..66435b60 100644 --- a/src/main/java/workspace/WorkspaceSideBarUi.java +++ b/src/main/java/workspace/WorkspaceSideBarUi.java @@ -11,295 +11,291 @@ public class WorkspaceSideBarUi extends UiComponent implements ModelListener { - private int xOffset = 10; - - private int yOffset = 65; - - private UiCheckBox gridCheckBox; - - private UiCheckBox faceNormalsCheckBox; - - private UiCheckBox vertexNormalsCheckBox; - - private UiCheckBox wireFrameCheckBox; - - private UiCheckBox xAxisCheckBox; - - private UiCheckBox yAxisCheckBox; - - private UiCheckBox zAxisCheckBox; - - private UiCheckBox edgeCheckBox; - - private UiCheckBox smoothShadingCheckBox; - - private UiCheckBox loopCheckBox; - - private UiLabel label; - - private WorkspaceModel model; - - public WorkspaceSideBarUi(WorkspaceModel model) { - this.model = model; - this.model.addListener(this); - createUI(); - } - - @Override - public void onModelChanged() { - gridCheckBox.setSelected(model.isGridVisible()); - faceNormalsCheckBox.setSelected(model.isFaceNormalsVisible()); - vertexNormalsCheckBox.setSelected(model.isVertexNormalsVisible()); - wireFrameCheckBox.setSelected(model.isWireframe()); - xAxisCheckBox.setSelected(model.isxAxisVisible()); - yAxisCheckBox.setSelected(model.isyAxisVisible()); - zAxisCheckBox.setSelected(model.iszAxisVisible()); - edgeCheckBox.setSelected(model.isEdgesVisible()); - smoothShadingCheckBox.setSelected(model.getShading() == Shading.SMOOTH); - loopCheckBox.setSelected(model.isLoop()); - } - - private void createUI() { - UiPanel panel = new UiPanel(); - panel.setWidth(200); - panel.setHeight(500); - panel.setBackground(new Color(77, 77, 77, 0)); - panel.add(getUiLabel()); - panel.add(getGridCheckBox()); - panel.add(getFaceNormalsCheckBox()); - panel.add(getVertexNormalsCheckBox()); - panel.add(getWireFrameCheckBox()); - panel.add(getXAxisCheckBox()); - panel.add(getYAxisCheckBox()); - panel.add(getZAxisCheckBox()); - panel.add(getEdgeCheckBox()); - panel.add(getSmoothShadingCheckBox()); - panel.add(getLoopCheckBox()); - add(panel); - } - - protected UiLabel getUiLabel() { - if (label != null) - return label; - - label = new UiLabel(); - label.setX(xOffset); - label.setY(yOffset); - label.setTitle("Controls:"); - label.setBackground(new Color(0, 0, 0, 0)); - label.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - return label; - } - - protected UiCheckBox getGridCheckBox() { - if (gridCheckBox != null) - return gridCheckBox; - - gridCheckBox = new UiCheckBox("Grid (G)"); - gridCheckBox.setX(xOffset); - gridCheckBox.setY(yOffset + 20); - gridCheckBox.setSelected(model.isGridVisible()); - gridCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - gridCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setGridVisible(gridCheckBox.isSelected()); - } - }); - - return gridCheckBox; - } - - protected UiCheckBox getFaceNormalsCheckBox() { - if (faceNormalsCheckBox != null) - return faceNormalsCheckBox; - - faceNormalsCheckBox = new UiCheckBox("Face Normals (N)"); - faceNormalsCheckBox.setX(xOffset); - faceNormalsCheckBox.setY(yOffset + 40); - faceNormalsCheckBox.setSelected(model.isFaceNormalsVisible()); - faceNormalsCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - faceNormalsCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setFaceNormalsVisible(faceNormalsCheckBox.isSelected()); - } - }); - - return faceNormalsCheckBox; - } - - protected UiCheckBox getVertexNormalsCheckBox() { - if (vertexNormalsCheckBox != null) - return vertexNormalsCheckBox; - - vertexNormalsCheckBox = new UiCheckBox("Vertex Normals (V)"); - vertexNormalsCheckBox.setX(xOffset); - vertexNormalsCheckBox.setY(yOffset + 60); - vertexNormalsCheckBox.setSelected(model.isVertexNormalsVisible()); - vertexNormalsCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - vertexNormalsCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setVertexNormalsVisible( - vertexNormalsCheckBox.isSelected() - ); - } - }); - - return vertexNormalsCheckBox; - } - - protected UiCheckBox getWireFrameCheckBox() { - if (wireFrameCheckBox != null) - return wireFrameCheckBox; - - wireFrameCheckBox = new UiCheckBox("Wireframe (Z)"); - wireFrameCheckBox.setX(xOffset); - wireFrameCheckBox.setY(yOffset + 80); - wireFrameCheckBox.setSelected(model.isWireframe()); - wireFrameCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - wireFrameCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setWireframe(wireFrameCheckBox.isSelected()); - } - }); - - return wireFrameCheckBox; - } - - protected UiCheckBox getXAxisCheckBox() { - if (xAxisCheckBox != null) - return xAxisCheckBox; - - xAxisCheckBox = new UiCheckBox("X-Axis (1)"); - xAxisCheckBox.setX(xOffset); - xAxisCheckBox.setY(yOffset + 100); - xAxisCheckBox.setSelected(model.isxAxisVisible()); - xAxisCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - xAxisCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setxAxisVisible(xAxisCheckBox.isSelected()); - } - }); - - return xAxisCheckBox; - } - - protected UiCheckBox getYAxisCheckBox() { - if (yAxisCheckBox != null) - return yAxisCheckBox; - - yAxisCheckBox = new UiCheckBox("Y-Axis (2)"); - yAxisCheckBox.setX(xOffset); - yAxisCheckBox.setY(yOffset + 120); - yAxisCheckBox.setSelected(model.isyAxisVisible()); - yAxisCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - yAxisCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setyAxisVisible(yAxisCheckBox.isSelected()); - } - }); - - return yAxisCheckBox; - } - - protected UiCheckBox getZAxisCheckBox() { - if (zAxisCheckBox != null) - return zAxisCheckBox; - - zAxisCheckBox = new UiCheckBox("Z-Axis (3)"); - zAxisCheckBox.setX(xOffset); - zAxisCheckBox.setY(yOffset + 140); - zAxisCheckBox.setSelected(model.iszAxisVisible()); - zAxisCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - zAxisCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setzAxisVisible(zAxisCheckBox.isSelected()); - } - }); - - return zAxisCheckBox; - } - - protected UiCheckBox getEdgeCheckBox() { - if (edgeCheckBox != null) - return edgeCheckBox; - - edgeCheckBox = new UiCheckBox("Edges (E)"); - edgeCheckBox.setX(xOffset); - edgeCheckBox.setY(yOffset + 160); - edgeCheckBox.setSelected(model.isEdgesVisible()); - edgeCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - edgeCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setEdgesVisible(edgeCheckBox.isSelected()); - } - }); - - return edgeCheckBox; - } - - protected UiCheckBox getSmoothShadingCheckBox() { - if (smoothShadingCheckBox != null) - return smoothShadingCheckBox; - - smoothShadingCheckBox = new UiCheckBox("Shade Smooth (S)"); - smoothShadingCheckBox.setX(xOffset); - smoothShadingCheckBox.setY(yOffset + 180); - smoothShadingCheckBox.setSelected(model.getShading() == Shading.SMOOTH); - smoothShadingCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - smoothShadingCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setShading( - smoothShadingCheckBox.isSelected() ? Shading.SMOOTH - : Shading.FLAT - ); - } - }); - - return smoothShadingCheckBox; - - } - - protected UiCheckBox getLoopCheckBox() { - if (loopCheckBox != null) - return loopCheckBox; - - loopCheckBox = new UiCheckBox("Loop"); - loopCheckBox.setX(xOffset); - loopCheckBox.setY(yOffset + 200); - loopCheckBox.setSelected(model.isLoop()); - loopCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); - loopCheckBox.setActionListener(new IActionListener() { - - @Override - public void onActionPerformed() { - model.setLoop(loopCheckBox.isSelected()); - } - }); - - return loopCheckBox; - } - - @Override - public boolean contains(int x, int y) { - return true; - } + private int xOffset = 10; + + private int yOffset = 65; + + private UiCheckBox gridCheckBox; + + private UiCheckBox faceNormalsCheckBox; + + private UiCheckBox vertexNormalsCheckBox; + + private UiCheckBox wireFrameCheckBox; + + private UiCheckBox xAxisCheckBox; + + private UiCheckBox yAxisCheckBox; + + private UiCheckBox zAxisCheckBox; + + private UiCheckBox edgeCheckBox; + + private UiCheckBox smoothShadingCheckBox; + + private UiCheckBox loopCheckBox; + + private UiLabel label; + + private WorkspaceModel model; + + public WorkspaceSideBarUi(WorkspaceModel model) { + this.model = model; + this.model.addListener(this); + createUI(); + } + + @Override + public void onModelChanged() { + gridCheckBox.setSelected(model.isGridVisible()); + faceNormalsCheckBox.setSelected(model.isFaceNormalsVisible()); + vertexNormalsCheckBox.setSelected(model.isVertexNormalsVisible()); + wireFrameCheckBox.setSelected(model.isWireframe()); + xAxisCheckBox.setSelected(model.isxAxisVisible()); + yAxisCheckBox.setSelected(model.isyAxisVisible()); + zAxisCheckBox.setSelected(model.iszAxisVisible()); + edgeCheckBox.setSelected(model.isEdgesVisible()); + smoothShadingCheckBox.setSelected(model.getShading() == Shading.SMOOTH); + loopCheckBox.setSelected(model.isLoop()); + } + + private void createUI() { + UiPanel panel = new UiPanel(); + panel.setWidth(200); + panel.setHeight(500); + panel.setBackground(new Color(77, 77, 77, 0)); + panel.add(getUiLabel()); + panel.add(getGridCheckBox()); + panel.add(getFaceNormalsCheckBox()); + panel.add(getVertexNormalsCheckBox()); + panel.add(getWireFrameCheckBox()); + panel.add(getXAxisCheckBox()); + panel.add(getYAxisCheckBox()); + panel.add(getZAxisCheckBox()); + panel.add(getEdgeCheckBox()); + panel.add(getSmoothShadingCheckBox()); + panel.add(getLoopCheckBox()); + add(panel); + } + + protected UiLabel getUiLabel() { + if (label != null) + return label; + + label = new UiLabel(); + label.setX(xOffset); + label.setY(yOffset); + label.setTitle("Controls:"); + label.setBackground(new Color(0, 0, 0, 0)); + label.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + return label; + } + + protected UiCheckBox getGridCheckBox() { + if (gridCheckBox != null) + return gridCheckBox; + + gridCheckBox = new UiCheckBox("Grid (G)"); + gridCheckBox.setX(xOffset); + gridCheckBox.setY(yOffset + 20); + gridCheckBox.setSelected(model.isGridVisible()); + gridCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + gridCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setGridVisible(gridCheckBox.isSelected()); + } + }); + + return gridCheckBox; + } + + protected UiCheckBox getFaceNormalsCheckBox() { + if (faceNormalsCheckBox != null) + return faceNormalsCheckBox; + + faceNormalsCheckBox = new UiCheckBox("Face Normals (N)"); + faceNormalsCheckBox.setX(xOffset); + faceNormalsCheckBox.setY(yOffset + 40); + faceNormalsCheckBox.setSelected(model.isFaceNormalsVisible()); + faceNormalsCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + faceNormalsCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setFaceNormalsVisible(faceNormalsCheckBox.isSelected()); + } + }); + + return faceNormalsCheckBox; + } + + protected UiCheckBox getVertexNormalsCheckBox() { + if (vertexNormalsCheckBox != null) + return vertexNormalsCheckBox; + + vertexNormalsCheckBox = new UiCheckBox("Vertex Normals (V)"); + vertexNormalsCheckBox.setX(xOffset); + vertexNormalsCheckBox.setY(yOffset + 60); + vertexNormalsCheckBox.setSelected(model.isVertexNormalsVisible()); + vertexNormalsCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + vertexNormalsCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setVertexNormalsVisible(vertexNormalsCheckBox.isSelected()); + } + }); + + return vertexNormalsCheckBox; + } + + protected UiCheckBox getWireFrameCheckBox() { + if (wireFrameCheckBox != null) + return wireFrameCheckBox; + + wireFrameCheckBox = new UiCheckBox("Wireframe (Z)"); + wireFrameCheckBox.setX(xOffset); + wireFrameCheckBox.setY(yOffset + 80); + wireFrameCheckBox.setSelected(model.isWireframe()); + wireFrameCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + wireFrameCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setWireframe(wireFrameCheckBox.isSelected()); + } + }); + + return wireFrameCheckBox; + } + + protected UiCheckBox getXAxisCheckBox() { + if (xAxisCheckBox != null) + return xAxisCheckBox; + + xAxisCheckBox = new UiCheckBox("X-Axis (1)"); + xAxisCheckBox.setX(xOffset); + xAxisCheckBox.setY(yOffset + 100); + xAxisCheckBox.setSelected(model.isxAxisVisible()); + xAxisCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + xAxisCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setxAxisVisible(xAxisCheckBox.isSelected()); + } + }); + + return xAxisCheckBox; + } + + protected UiCheckBox getYAxisCheckBox() { + if (yAxisCheckBox != null) + return yAxisCheckBox; + + yAxisCheckBox = new UiCheckBox("Y-Axis (2)"); + yAxisCheckBox.setX(xOffset); + yAxisCheckBox.setY(yOffset + 120); + yAxisCheckBox.setSelected(model.isyAxisVisible()); + yAxisCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + yAxisCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setyAxisVisible(yAxisCheckBox.isSelected()); + } + }); + + return yAxisCheckBox; + } + + protected UiCheckBox getZAxisCheckBox() { + if (zAxisCheckBox != null) + return zAxisCheckBox; + + zAxisCheckBox = new UiCheckBox("Z-Axis (3)"); + zAxisCheckBox.setX(xOffset); + zAxisCheckBox.setY(yOffset + 140); + zAxisCheckBox.setSelected(model.iszAxisVisible()); + zAxisCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + zAxisCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setzAxisVisible(zAxisCheckBox.isSelected()); + } + }); + + return zAxisCheckBox; + } + + protected UiCheckBox getEdgeCheckBox() { + if (edgeCheckBox != null) + return edgeCheckBox; + + edgeCheckBox = new UiCheckBox("Edges (E)"); + edgeCheckBox.setX(xOffset); + edgeCheckBox.setY(yOffset + 160); + edgeCheckBox.setSelected(model.isEdgesVisible()); + edgeCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + edgeCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setEdgesVisible(edgeCheckBox.isSelected()); + } + }); + + return edgeCheckBox; + } + + protected UiCheckBox getSmoothShadingCheckBox() { + if (smoothShadingCheckBox != null) + return smoothShadingCheckBox; + + smoothShadingCheckBox = new UiCheckBox("Shade Smooth (S)"); + smoothShadingCheckBox.setX(xOffset); + smoothShadingCheckBox.setY(yOffset + 180); + smoothShadingCheckBox.setSelected(model.getShading() == Shading.SMOOTH); + smoothShadingCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + smoothShadingCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setShading( + smoothShadingCheckBox.isSelected() ? Shading.SMOOTH : Shading.FLAT); + } + }); + + return smoothShadingCheckBox; + + } + + protected UiCheckBox getLoopCheckBox() { + if (loopCheckBox != null) + return loopCheckBox; + + loopCheckBox = new UiCheckBox("Loop"); + loopCheckBox.setX(xOffset); + loopCheckBox.setY(yOffset + 200); + loopCheckBox.setSelected(model.isLoop()); + loopCheckBox.setForeground(UiValues.UI_ELEMENT_FOREGROUND); + loopCheckBox.setActionListener(new IActionListener() { + + @Override + public void onActionPerformed() { + model.setLoop(loopCheckBox.isSelected()); + } + }); + + return loopCheckBox; + } + + @Override + public boolean contains(int x, int y) { + return true; + } } From b9db88953714dd0476738ad22480ae39675df5de Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 09:24:21 +0100 Subject: [PATCH 04/15] Refactor ViewGizmo for improved modularity and deprecation compliance Introduced default values for size and height to simplify initialization. Replaced deprecated mesh operations with TranslateModifier and RotateModifier for cone transformations. Modularized rendering logic by introducing renderMesh to reduce redundancy and improve readability. Centralized cone creation logic in createCone for better code reuse and consistency. Enhanced method and variable naming for clarity. --- src/main/java/workspace/ui/ViewGizmo.java | 201 ++++++++++++---------- 1 file changed, 108 insertions(+), 93 deletions(-) diff --git a/src/main/java/workspace/ui/ViewGizmo.java b/src/main/java/workspace/ui/ViewGizmo.java index a32d9215..9c9960f4 100644 --- a/src/main/java/workspace/ui/ViewGizmo.java +++ b/src/main/java/workspace/ui/ViewGizmo.java @@ -5,104 +5,119 @@ import mesh.creator.primitives.ConeCreator; import mesh.creator.primitives.CubeCreator; import mesh.modifier.RotateXModifier; +import mesh.modifier.RotateZModifier; import mesh.modifier.ScaleModifier; +import mesh.modifier.TranslateModifier; import workspace.laf.UiConstants; import workspace.laf.UiValues; public class ViewGizmo extends UiComponent { - private float height = 2; - - private float size = 10; - - private float rotationX; - - private float rotationY; - - private float rotationZ; - - private Mesh3D cube; - - private Mesh3D coneX; - - private Mesh3D coneY; - - private Mesh3D coneZ; - - public ViewGizmo() { - createMeshes(); - } - - public void draw(Graphics g) { - g.pushMatrix(); - g.translate(x, y); - g.rotateX(rotationX); - g.rotateY(rotationY); - g.rotateZ(rotationZ); - g.setColor(UiValues.getColor(UiConstants.KEY_GIZMO_CENTER_COLOR)); - g.fillFaces(cube); - g.setColor(UiValues.getColor(UiConstants.KEY_GIZMO_AXIS_X_COLOR)); - g.fillFaces(coneX); - g.setColor(UiValues.getColor(UiConstants.KEY_GIZMO_AXIS_Y_COLOR)); - g.fillFaces(coneY); - g.setColor(UiValues.getColor(UiConstants.KEY_GIZMO_AXIS_Z_COLOR)); - g.fillFaces(coneZ); - g.popMatrix(); - } - - private void createMeshes() { - createCube(); - createConeX(); - createConeY(); - createConeZ(); - } - - private void createConeX() { - coneX = new ConeCreator().create(); - coneX.apply(new ScaleModifier(size)); - coneX.rotateZ(-Mathf.HALF_PI); - coneX.translateX(height * size); - } - - private void createConeY() { - coneY = new ConeCreator().create(); - coneY.apply(new ScaleModifier(size)); - coneY.translateY(height * size); - } - - private void createConeZ() { - coneZ = new ConeCreator().create(); - coneZ.apply(new ScaleModifier(size)); - coneZ.apply(new RotateXModifier(Mathf.HALF_PI)); - coneZ.translateZ(height * size); - } - - private void createCube() { - cube = new CubeCreator(size).create(); - } - - public float getRotationX() { - return rotationX; - } - - public void setRotationX(float rotationX) { - this.rotationX = rotationX; - } - - public float getRotationY() { - return rotationY; - } - - public void setRotationY(float rotationY) { - this.rotationY = rotationY; - } - - public float getRotationZ() { - return rotationZ; - } - - public void setRotationZ(float rotationZ) { - this.rotationZ = rotationZ; - } + private static final float DEFAULT_HEIGHT = 2; + + private static final float DEFAULT_SIZE = 10; + + private float height; + + private float size; + + private float rotationX; + + private float rotationY; + + private float rotationZ; + + private Mesh3D cube; + + private Mesh3D coneX; + + private Mesh3D coneY; + + private Mesh3D coneZ; + + public ViewGizmo() { + this.height = DEFAULT_HEIGHT; + this.size = DEFAULT_SIZE; + createMeshes(); + } + + @Override + public void render(Graphics g) { + g.pushMatrix(); + g.translate(x, y); + g.rotateX(rotationX); + g.rotateY(rotationY); + g.rotateZ(rotationZ); + + renderMesh(g, cube, UiConstants.KEY_GIZMO_CENTER_COLOR); + renderMesh(g, coneX, UiConstants.KEY_GIZMO_AXIS_X_COLOR); + renderMesh(g, coneY, UiConstants.KEY_GIZMO_AXIS_Y_COLOR); + renderMesh(g, coneZ, UiConstants.KEY_GIZMO_AXIS_Z_COLOR); + + g.popMatrix(); + } + + private void renderMesh(Graphics g, Mesh3D mesh, String colorKey) { + g.setColor(UiValues.getColor(colorKey)); + g.fillFaces(mesh); + } + + private void createMeshes() { + createCube(); + createConeX(); + createConeY(); + createConeZ(); + } + + private void createConeX() { + coneX = createCone(); + coneX.apply(new RotateZModifier(-Mathf.HALF_PI)); + coneX.apply(new TranslateModifier(height * size, 0, 0)); + } + + private void createConeY() { + coneY = createCone(); + coneY.apply(new TranslateModifier(0, height * size, 0)); + } + + private void createConeZ() { + coneZ = createCone(); + coneZ.apply(new RotateXModifier(Mathf.HALF_PI)); + coneZ.apply(new TranslateModifier(0, 0, height * size)); + } + + private Mesh3D createCone() { + Mesh3D cone = new ConeCreator().create(); + cone.apply(new ScaleModifier(size)); + return cone; + } + + private void createCube() { + cube = new CubeCreator(size).create(); + } + + public float getRotationX() { + return rotationX; + } + + public void setRotationX(float rotationX) { + this.rotationX = rotationX; + } + + public float getRotationY() { + return rotationY; + } + + public void setRotationY(float rotationY) { + this.rotationY = rotationY; + } + + public float getRotationZ() { + return rotationZ; + } + + public void setRotationZ(float rotationZ) { + this.rotationZ = rotationZ; + } } From ad44ab83ca592ebc557cad6cefebb0663e3e12b6 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 09:28:19 +0100 Subject: [PATCH 05/15] Rename ViewGizmo to ViewportCompass - Updated class name from `ViewGizmo` to `ViewportCompass` to better reflect its purpose as a non-interactive orientation indicator for the viewport. - Adjusted references and comments to align with the new name. --- .../java/workspace/ui/{ViewGizmo.java => ViewportCompass.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/workspace/ui/{ViewGizmo.java => ViewportCompass.java} (100%) diff --git a/src/main/java/workspace/ui/ViewGizmo.java b/src/main/java/workspace/ui/ViewportCompass.java similarity index 100% rename from src/main/java/workspace/ui/ViewGizmo.java rename to src/main/java/workspace/ui/ViewportCompass.java From fcfb5c85208676fccdd8875570ee3e7455c13ea8 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 09:45:26 +0100 Subject: [PATCH 06/15] =?UTF-8?q?Refactor:=20Consolidate=20rotation=20logi?= =?UTF-8?q?c=20with=20Vector3f=20-=20Switched=20to=20`Vector3f`=20for=20cl?= =?UTF-8?q?eaner=20rotation=20handling.=20-=20Centralized=20rotation=20upd?= =?UTF-8?q?ates=20using=20`setRotation(Vector3f)`=20with=20clamping=20logi?= =?UTF-8?q?c=20to=20ensure=20safe=20ranges=20(-=CF=80,=20=CF=80).=20-=20Re?= =?UTF-8?q?moved=20individual=20axis=20setter=20methods=20for=20simplicity?= =?UTF-8?q?=20and=20better=20maintainability.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/workspace/ui/ViewportCompass.java | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/src/main/java/workspace/ui/ViewportCompass.java b/src/main/java/workspace/ui/ViewportCompass.java index 9c9960f4..f3fc676c 100644 --- a/src/main/java/workspace/ui/ViewportCompass.java +++ b/src/main/java/workspace/ui/ViewportCompass.java @@ -1,6 +1,7 @@ package workspace.ui; import math.Mathf; +import math.Vector3f; import mesh.Mesh3D; import mesh.creator.primitives.ConeCreator; import mesh.creator.primitives.CubeCreator; @@ -11,7 +12,7 @@ import workspace.laf.UiConstants; import workspace.laf.UiValues; -public class ViewGizmo extends UiComponent { +public class ViewportCompass extends UiComponent { private static final float DEFAULT_HEIGHT = 2; @@ -21,11 +22,7 @@ public class ViewGizmo extends UiComponent { private float size; - private float rotationX; - - private float rotationY; - - private float rotationZ; + private Vector3f rotation; private Mesh3D cube; @@ -35,9 +32,10 @@ public class ViewGizmo extends UiComponent { private Mesh3D coneZ; - public ViewGizmo() { + public ViewportCompass() { this.height = DEFAULT_HEIGHT; this.size = DEFAULT_SIZE; + this.rotation = new Vector3f(); createMeshes(); } @@ -45,9 +43,9 @@ public ViewGizmo() { public void render(Graphics g) { g.pushMatrix(); g.translate(x, y); - g.rotateX(rotationX); - g.rotateY(rotationY); - g.rotateZ(rotationZ); + g.rotateX(rotation.x); + g.rotateY(rotation.y); + g.rotateZ(rotation.z); renderMesh(g, cube, UiConstants.KEY_GIZMO_CENTER_COLOR); renderMesh(g, coneX, UiConstants.KEY_GIZMO_AXIS_X_COLOR); @@ -96,28 +94,10 @@ private void createCube() { cube = new CubeCreator(size).create(); } - public float getRotationX() { - return rotationX; - } - - public void setRotationX(float rotationX) { - this.rotationX = rotationX; - } - - public float getRotationY() { - return rotationY; - } - - public void setRotationY(float rotationY) { - this.rotationY = rotationY; - } - - public float getRotationZ() { - return rotationZ; - } - - public void setRotationZ(float rotationZ) { - this.rotationZ = rotationZ; + public void setRotation(Vector3f rotation) { + this.rotation.setX(Mathf.clamp(rotation.x, -Mathf.PI, Mathf.PI)); + this.rotation.setY(Mathf.clamp(rotation.y, -Mathf.PI, Mathf.PI)); + this.rotation.setZ(Mathf.clamp(rotation.z, -Mathf.PI, Mathf.PI)); } } From 76844628451288abbf1ffe4772511c9a6c82b3f1 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 09:49:45 +0100 Subject: [PATCH 07/15] Add JavaDoc comments to ViewportCompass class and its methods - Documented the purpose of the `ViewportCompass` class. - Added JavaDoc for all methods to explain their purpose, parameters, and logic. - Improved maintainability and developer clarity by providing context for each helper method and transformations. --- .../java/workspace/ui/ViewportCompass.java | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/main/java/workspace/ui/ViewportCompass.java b/src/main/java/workspace/ui/ViewportCompass.java index f3fc676c..445c4624 100644 --- a/src/main/java/workspace/ui/ViewportCompass.java +++ b/src/main/java/workspace/ui/ViewportCompass.java @@ -12,16 +12,22 @@ import workspace.laf.UiConstants; import workspace.laf.UiValues; +/** + * Represents a simple viewport compass in the upper-right corner of the mesh + * viewer UI. The compass visualizes the current rotation/orientation of the + * viewport in 3D space. This component is non-interactive and purely for + * visualization purposes. + */ public class ViewportCompass extends UiComponent { private static final float DEFAULT_HEIGHT = 2; private static final float DEFAULT_SIZE = 10; - private float height; - private float size; + private float height; + private Vector3f rotation; private Mesh3D cube; @@ -32,13 +38,24 @@ public class ViewportCompass extends UiComponent { private Mesh3D coneZ; + /** + * Default constructor initializes the compass with default height, size, and + * creates all required meshes for rendering the compass. + */ public ViewportCompass() { - this.height = DEFAULT_HEIGHT; this.size = DEFAULT_SIZE; + this.height = DEFAULT_HEIGHT; this.rotation = new Vector3f(); createMeshes(); } + /** + * Renders the viewport compass in the UI's graphics context. This method sets + * the translation, applies rotations, and renders the gizmo axes and center + * mesh. + * + * @param g The graphics context for rendering. + */ @Override public void render(Graphics g) { g.pushMatrix(); @@ -55,11 +72,21 @@ public void render(Graphics g) { g.popMatrix(); } + /** + * Helper method to render a given mesh with its associated color key. + * + * @param g The graphics context for rendering. + * @param mesh The mesh to render. + * @param colorKey The key identifying the color to use from UiValues. + */ private void renderMesh(Graphics g, Mesh3D mesh, String colorKey) { g.setColor(UiValues.getColor(colorKey)); g.fillFaces(mesh); } + /** + * Creates and initializes all necessary meshes for the compass visualization. + */ private void createMeshes() { createCube(); createConeX(); @@ -67,33 +94,60 @@ private void createMeshes() { createConeZ(); } + /** + * Creates the X-axis cone and applies necessary transformations to position + * and rotate it. + */ private void createConeX() { coneX = createCone(); coneX.apply(new RotateZModifier(-Mathf.HALF_PI)); coneX.apply(new TranslateModifier(height * size, 0, 0)); } + /** + * Creates the Y-axis cone and applies necessary transformations to position + * it in the Y direction. + */ private void createConeY() { coneY = createCone(); coneY.apply(new TranslateModifier(0, height * size, 0)); } + /** + * Creates the Z-axis cone and applies transformations to rotate and position + * it in the Z direction. + */ private void createConeZ() { coneZ = createCone(); coneZ.apply(new RotateXModifier(Mathf.HALF_PI)); coneZ.apply(new TranslateModifier(0, 0, height * size)); } + /** + * Creates a cone mesh, scales it appropriately, and prepares it for + * rendering. + * + * @return The scaled and ready-to-render cone mesh. + */ private Mesh3D createCone() { Mesh3D cone = new ConeCreator().create(); cone.apply(new ScaleModifier(size)); return cone; } + /** + * Creates the central cube for visualization. + */ private void createCube() { cube = new CubeCreator(size).create(); } + /** + * Sets the viewport's rotation, clamping each rotation axis value to ensure + * valid ranges. + * + * @param rotation The desired rotation vector. + */ public void setRotation(Vector3f rotation) { this.rotation.setX(Mathf.clamp(rotation.x, -Mathf.PI, Mathf.PI)); this.rotation.setY(Mathf.clamp(rotation.y, -Mathf.PI, Mathf.PI)); From e0f2ca3c1d437e4aee445faed67536f1abd403bc Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 10:00:34 +0100 Subject: [PATCH 08/15] Fix: Removed clamping. Did not behave as expected. --- src/main/java/workspace/ui/ViewportCompass.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/workspace/ui/ViewportCompass.java b/src/main/java/workspace/ui/ViewportCompass.java index 445c4624..b143ea99 100644 --- a/src/main/java/workspace/ui/ViewportCompass.java +++ b/src/main/java/workspace/ui/ViewportCompass.java @@ -143,15 +143,14 @@ private void createCube() { } /** - * Sets the viewport's rotation, clamping each rotation axis value to ensure - * valid ranges. + * Sets the viewport's rotation. * * @param rotation The desired rotation vector. */ public void setRotation(Vector3f rotation) { - this.rotation.setX(Mathf.clamp(rotation.x, -Mathf.PI, Mathf.PI)); - this.rotation.setY(Mathf.clamp(rotation.y, -Mathf.PI, Mathf.PI)); - this.rotation.setZ(Mathf.clamp(rotation.z, -Mathf.PI, Mathf.PI)); + this.rotation.setX(rotation.x); + this.rotation.setY(rotation.y); + this.rotation.setZ(rotation.z); } } From 842d012879b434e7ea8177be9a2fa69e381161a9 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 12:41:03 +0100 Subject: [PATCH 09/15] Refactor: Transition UI Framework to Abstract, Flexible Design - Restructured package hierarchy to better organize framework components. - Decoupled core UI logic from Processing-specific implementations, paving the way for broader flexibility and rendering backends. - Began transforming library from a prototype to a robust, extensible UI framework. - Introduced abstract base classes and interfaces for key components. - Updated existing components to align with the new architecture. - Enhanced modularity by improving separation of concerns: - Moved rendering logic to dedicated `Renderer` classes. - Improved code clarity and maintainability with better encapsulation and naming conventions. - Added comprehensive JavaDocs to foundational classes for improved readability and usability. This commit marks the foundational shift from a prototype to a polished framework capable of supporting future scalability and flexibility. Follow-up commits will address integration, performance tuning, and additional feature development. --- src/main/java/workspace/Editor.java | 292 +++--- src/main/java/workspace/FirstPersonView.java | 197 ++-- src/main/java/workspace/GraphicsPImpl.java | 326 +++--- src/main/java/workspace/ModelListener.java | 2 +- src/main/java/workspace/SceneObject.java | 62 +- src/main/java/workspace/Workspace.java | 967 +++++++++--------- src/main/java/workspace/WorkspaceModel.java | 592 +++++------ .../java/workspace/WorkspaceSideBarUi.java | 10 +- src/main/java/workspace/laf/LookAndFeel.java | 67 +- src/main/java/workspace/laf/UiConstants.java | 26 +- src/main/java/workspace/laf/UiValues.java | 42 +- src/main/java/workspace/ui/Color.java | 114 +-- src/main/java/workspace/ui/Graphics.java | 54 +- src/main/java/workspace/ui/Slider.java | 145 +-- src/main/java/workspace/ui/UiComponent.java | 446 ++++---- src/main/java/workspace/ui/UiSimpleList.java | 80 +- src/main/java/workspace/ui/border/Border.java | 32 +- .../workspace/ui/border/CompoundBorder.java | 96 ++ .../workspace/ui/border/CornerGapBorder.java | 172 ++++ .../java/workspace/ui/border/EmptyBorder.java | 53 + src/main/java/workspace/ui/border/Insets.java | 418 ++++---- .../java/workspace/ui/border/LineBorder.java | 167 ++- .../workspace/ui/{ => elements}/UiButton.java | 0 .../ui/{ => elements}/UiCheckBox.java | 0 .../ui/{ => elements}/UiEditorMenu.java | 0 .../java/workspace/ui/elements/UiElement.java | 170 +++ .../workspace/ui/{ => elements}/UiLabel.java | 0 .../workspace/ui/{ => elements}/UiPanel.java | 0 .../ui/{ => elements}/ViewportCompass.java | 0 .../ui/{ => event}/IActionListener.java | 0 .../ui/{ => event}/ISliderCallBack.java | 0 .../workspace/ui/{ => event}/MouseEvent.java | 0 src/main/java/workspace/ui/layout/Layout.java | 37 +- .../workspace/ui/renderer/ButtonRenderer.java | 44 + .../ui/renderer/CheckBoxRenderer.java | 45 + .../ui/renderer/EditorMenuRenderer.java | 46 + .../workspace/ui/renderer/LabelRenderer.java | 44 + .../workspace/ui/renderer/PanelRenderer.java | 45 + .../java/workspace/ui/renderer/Renderer.java | 42 + 39 files changed, 2882 insertions(+), 1951 deletions(-) create mode 100644 src/main/java/workspace/ui/border/CompoundBorder.java create mode 100644 src/main/java/workspace/ui/border/CornerGapBorder.java create mode 100644 src/main/java/workspace/ui/border/EmptyBorder.java rename src/main/java/workspace/ui/{ => elements}/UiButton.java (100%) rename src/main/java/workspace/ui/{ => elements}/UiCheckBox.java (100%) rename src/main/java/workspace/ui/{ => elements}/UiEditorMenu.java (100%) create mode 100644 src/main/java/workspace/ui/elements/UiElement.java rename src/main/java/workspace/ui/{ => elements}/UiLabel.java (100%) rename src/main/java/workspace/ui/{ => elements}/UiPanel.java (100%) rename src/main/java/workspace/ui/{ => elements}/ViewportCompass.java (100%) rename src/main/java/workspace/ui/{ => event}/IActionListener.java (100%) rename src/main/java/workspace/ui/{ => event}/ISliderCallBack.java (100%) rename src/main/java/workspace/ui/{ => event}/MouseEvent.java (100%) create mode 100644 src/main/java/workspace/ui/renderer/ButtonRenderer.java create mode 100644 src/main/java/workspace/ui/renderer/CheckBoxRenderer.java create mode 100644 src/main/java/workspace/ui/renderer/EditorMenuRenderer.java create mode 100644 src/main/java/workspace/ui/renderer/LabelRenderer.java create mode 100644 src/main/java/workspace/ui/renderer/PanelRenderer.java create mode 100644 src/main/java/workspace/ui/renderer/Renderer.java diff --git a/src/main/java/workspace/Editor.java b/src/main/java/workspace/Editor.java index 1fc060eb..0c24edaf 100644 --- a/src/main/java/workspace/Editor.java +++ b/src/main/java/workspace/Editor.java @@ -18,150 +18,158 @@ import workspace.command.WireframeCommand; import workspace.laf.LookAndFeel; import workspace.ui.UiComponent; -import workspace.ui.UiEditorMenu; -import workspace.ui.ViewGizmo; +import workspace.ui.elements.UiEditorMenu; +import workspace.ui.elements.ViewportCompass; public class Editor implements ModelListener { - protected List sceneObjects; - - protected UiComponent rootUi; - - protected KeyCommandMap commands; - - protected WorkspaceModel model; - - protected ViewGizmo gizmo; - - protected WorkspaceSideBarUi sideBar; - - protected UiEditorMenu menu; - - public Editor() { - setup(); - } - - private void setup() { - setupLookAndFeel(); - initializeModel(); - initializeSceneObjects(); - initializeRootUi(); - createUi(); - initializeCommandMap(); - registerKeyCommands(); - } - - @Override - public void onModelChanged() { - rootUi.setVisible(model.isUiVisible()); - } - - private void initializeSceneObjects() { - sceneObjects = new ArrayList(); - } - - private void createUi() { - rootUi.add(getSideBar()); - rootUi.add(getGizmo()); - rootUi.add(getMenu()); - } - - private WorkspaceSideBarUi getSideBar() { - if (sideBar == null) { - sideBar = new WorkspaceSideBarUi(model); - } - return sideBar; - } - - private ViewGizmo getGizmo() { - if (gizmo == null) { - gizmo = new ViewGizmo(); - } - return gizmo; - } - - private UiEditorMenu getMenu() { - if (menu == null) { - menu = new UiEditorMenu(); - } - return menu; - } - - private void initializeRootUi() { - rootUi = new UiComponent(); - } - - private void setupLookAndFeel() { - LookAndFeel.setup(); - } - - private void initializeCommandMap() { - commands = new KeyCommandMap(); - } - - private void initializeModel() { - model = new WorkspaceModel(); - } - - private void registerKeyCommands() { - commands.register(new ShowHideGridCommand(model)); - commands.register(new ShowHideXAxisCommand(model)); - commands.register(new ShowHideYAxisCommand(model)); - commands.register(new ShowHideZAxisCommand(model)); - commands.register(new ShowHideSideBarCommand(model)); - commands.register(new ShowHideFaceNormalsCommand(model)); - commands.register(new ResetPanningCommand(model)); - commands.register(new ShowHideVertexNormalsCommand(model)); - commands.register(new ShowHideEdgesCommand(model)); - commands.register(new WireframeCommand(model)); - commands.register(new ShadeSmoothFlatCommand(model)); - } - - private void resizeRootUi(int x, int y, int width, int height) { - rootUi.setX(x); - rootUi.setY(y); - rootUi.setWidth(width); - rootUi.setHeight(height); - } - - public void resize(int x, int y, int width, int height) { - resizeRootUi(x, y, width, height); - updateGizmo(width, height); - } - - public void handleMouseClicked(int x, int y) { - rootUi.onMouseClicked(x, y); - } - - public void handleMouseDragged(int x, int y) { - rootUi.onMouseDragged(x, y); - } - - public void handleMouseWheel(float amount) { - float scale = model.getScale(); - scale -= amount * scale * 0.2f; - model.setScale(scale); - } - - private void updateGizmo(int width, int height) { - gizmo.setX(width - 80); - gizmo.setY(130); - } - - public void add(UiComponent component) { - rootUi.add(component); - } - - public void addSceneObject(SceneObject sceneObject) { - sceneObjects.add(sceneObject); - } - - public void addAll(Collection sceneObjects) { - this.sceneObjects.addAll(sceneObjects); - } - - public void clearSceneObjects() { - sceneObjects.clear(); - } + protected List sceneObjects; + + protected UiComponent rootUi; + + protected KeyCommandMap commands; + + protected WorkspaceModel model; + + protected ViewportCompass gizmo; + + protected WorkspaceSideBarUi sideBar; + + protected UiEditorMenu menu; + + public Editor() { + setup(); + } + + private void setup() { + setupLookAndFeel(); + initializeModel(); + initializeSceneObjects(); + initializeRootUi(); + createUi(); + initializeCommandMap(); + registerKeyCommands(); + } + + @Override + public void onModelChanged() { + rootUi.setVisible(model.isUiVisible()); + } + + private void initializeSceneObjects() { + sceneObjects = new ArrayList(); + } + + private void createUi() { + rootUi.add(getSideBar()); + rootUi.add(getGizmo()); + rootUi.add(getMenu()); + } + + private WorkspaceSideBarUi getSideBar() { + if (sideBar == null) { + sideBar = new WorkspaceSideBarUi(model); + } + return sideBar; + } + + private ViewportCompass getGizmo() { + if (gizmo == null) { + gizmo = new ViewportCompass(); + } + return gizmo; + } + + private UiEditorMenu getMenu() { + if (menu == null) { + menu = new UiEditorMenu(); + } + return menu; + } + + private void initializeRootUi() { + rootUi = new UiComponent(); + } + + private void setupLookAndFeel() { + LookAndFeel.setup(); + } + + private void initializeCommandMap() { + commands = new KeyCommandMap(); + } + + private void initializeModel() { + model = new WorkspaceModel(); + } + + private void registerKeyCommands() { + commands.register(new ShowHideGridCommand(model)); + commands.register(new ShowHideXAxisCommand(model)); + commands.register(new ShowHideYAxisCommand(model)); + commands.register(new ShowHideZAxisCommand(model)); + commands.register(new ShowHideSideBarCommand(model)); + commands.register(new ShowHideFaceNormalsCommand(model)); + commands.register(new ResetPanningCommand(model)); + commands.register(new ShowHideVertexNormalsCommand(model)); + commands.register(new ShowHideEdgesCommand(model)); + commands.register(new WireframeCommand(model)); + commands.register(new ShadeSmoothFlatCommand(model)); + } + + private void resizeRootUi(int x, int y, int width, int height) { + rootUi.setX(x); + rootUi.setY(y); + rootUi.setWidth(width); + rootUi.setHeight(height); + } + + public void resize(int x, int y, int width, int height) { + resizeRootUi(x, y, width, height); + updateGizmo(width, height); + } + + public void handleMouseClicked(int x, int y) { + rootUi.onMouseClicked(x, y); + } + + public void handleMousePressed(int x, int y) { + rootUi.onMousePressed(x, y); + } + + public void handleMouseDragged(int x, int y) { + rootUi.onMouseDragged(x, y); + } + + public void handleMouseReleased(int x, int y) { + rootUi.onMouseReleased(x, y); + } + + public void handleMouseWheel(float amount) { + float scale = model.getScale(); + scale -= amount * scale * 0.2f; + model.setScale(scale); + } + + private void updateGizmo(int width, int height) { + gizmo.setX(width - 80); + gizmo.setY(130); + } + + public void add(UiComponent component) { + rootUi.add(component); + } + + public void addSceneObject(SceneObject sceneObject) { + sceneObjects.add(sceneObject); + } + + public void addAll(Collection sceneObjects) { + this.sceneObjects.addAll(sceneObjects); + } + + public void clearSceneObjects() { + sceneObjects.clear(); + } } diff --git a/src/main/java/workspace/FirstPersonView.java b/src/main/java/workspace/FirstPersonView.java index 6a94deab..d6b71a7e 100644 --- a/src/main/java/workspace/FirstPersonView.java +++ b/src/main/java/workspace/FirstPersonView.java @@ -10,148 +10,145 @@ public class FirstPersonView { - private boolean enabled; + private boolean enabled; - private boolean left; + private boolean left; - private boolean right; + private boolean right; - private boolean forward; + private boolean forward; - private boolean back; + private boolean back; - private boolean up; + private boolean up; - private boolean down; + private boolean down; - private float pitch = Mathf.PI; + private float pitch = Mathf.PI; - private float yaw = 0; + private float yaw = 0; - private Vector3f eye = new Vector3f(-1000, 0, 1000); + private Vector3f eye = new Vector3f(-1000, 0, 1000); - private float speed = 10; + private float speed = 10; - private PApplet context; + private PApplet context; - public FirstPersonView(PApplet context) { - this.context = context; - context.registerMethod("pre", this); - context.registerMethod("keyEvent", this); - } + public FirstPersonView(PApplet context) { + this.context = context; + context.registerMethod("pre", this); + context.registerMethod("keyEvent", this); + } - public void pre() { - if (!enabled) - return; - yaw = Mathf.map(context.mouseX, 0, context.width, Mathf.PI, -Mathf.PI); - pitch = Mathf - .map(context.mouseY, 0, context.height, -Mathf.PI, Mathf.PI); + public void pre() { + if (!enabled) + return; + yaw = Mathf.map(context.mouseX, 0, context.width, Mathf.PI, -Mathf.PI); + pitch = Mathf.map(context.mouseY, 0, context.height, -Mathf.PI, Mathf.PI); // if (pitch > 89) // pitch = 89; // if (pitch < -89) // pitch = -89; - Vector3f front = new Vector3f(); - float x = Mathf.cos(Mathf.toRadians(yaw)) - * Mathf.cos(Mathf.toRadians(pitch)); - float y = Mathf.sin(Mathf.toRadians(pitch)); - float z = Mathf.cos(Mathf.toRadians(yaw)) - * Mathf.cos(Mathf.toRadians(pitch)); - front.set(x, y, z); + Vector3f front = new Vector3f(); + float x = Mathf.cos(Mathf.toRadians(yaw)) + * Mathf.cos(Mathf.toRadians(pitch)); + float y = Mathf.sin(Mathf.toRadians(pitch)); + float z = Mathf.cos(Mathf.toRadians(yaw)) + * Mathf.cos(Mathf.toRadians(pitch)); + front.set(x, y, z); - Vector3f velocity = new Vector3f(); + Vector3f velocity = new Vector3f(); - if (left) { - velocity.addLocal(-1, 0, 0); - } + if (left) { + velocity.addLocal(-1, 0, 0); + } - if (right) { - velocity.addLocal(1, 0, 0); - } + if (right) { + velocity.addLocal(1, 0, 0); + } - if (back) { - velocity.addLocal(0, 0, 1); - } + if (back) { + velocity.addLocal(0, 0, 1); + } - if (forward) { - velocity.addLocal(0, 0, -1); - } + if (forward) { + velocity.addLocal(0, 0, -1); + } - velocity.multLocal(getRotationMatrix(yaw)); + velocity.multLocal(getRotationMatrix(yaw)); - eye.addLocal(velocity.mult(speed)); - eye.setY(-300); - } + eye.addLocal(velocity.mult(speed)); + eye.setY(-300); + } - public void apply() { - Matrix4f m = Matrix4f.fpsViewRH(eye, pitch, yaw).transpose(); - PMatrix matrix = context.getMatrix(); - matrix.set(m.getValues()); - context.setMatrix(matrix); - } + public void apply() { + Matrix4f m = Matrix4f.fpsViewRH(eye, pitch, yaw).transpose(); + PMatrix matrix = context.getMatrix(); + matrix.set(m.getValues()); + context.setMatrix(matrix); + } - public void keyEvent(KeyEvent key) { - if (key.getAction() == KeyEvent.PRESS) - onKeyPressed(key.getKey()); - if (key.getAction() == KeyEvent.RELEASE) - onKeyReleased(key.getKey()); - } + public void keyEvent(KeyEvent key) { + if (key.getAction() == KeyEvent.PRESS) + onKeyPressed(key.getKey()); + if (key.getAction() == KeyEvent.RELEASE) + onKeyReleased(key.getKey()); + } - public void onKeyPressed(char key) { - if (key == 'w' || key == 'W') - forward = true; + public void onKeyPressed(char key) { + if (key == 'w' || key == 'W') + forward = true; - if (key == 's' || key == 'S') - back = true; + if (key == 's' || key == 'S') + back = true; - if (key == 'a' || key == 'A') - left = true; + if (key == 'a' || key == 'A') + left = true; - if (key == 'd' || key == 'D') - right = true; + if (key == 'd' || key == 'D') + right = true; - if (key == ' ') - up = true; + if (key == ' ') + up = true; - if (key == 'c' || key == 'C') - down = true; - } + if (key == 'c' || key == 'C') + down = true; + } - public void onKeyReleased(char key) { - if (key == 'w' || key == 'W') - forward = false; + public void onKeyReleased(char key) { + if (key == 'w' || key == 'W') + forward = false; - if (key == 's' || key == 'S') - back = false; + if (key == 's' || key == 'S') + back = false; - if (key == 'a' || key == 'A') - left = false; + if (key == 'a' || key == 'A') + left = false; - if (key == 'd' || key == 'D') - right = false; + if (key == 'd' || key == 'D') + right = false; - if (key == ' ') - up = false; + if (key == ' ') + up = false; - if (key == 'c' || key == 'C') - down = false; - } + if (key == 'c' || key == 'C') + down = false; + } - public Matrix3f getRotationMatrix(float angle) { - Matrix3f m = new Matrix3f( - Mathf.cos(angle), 0, Mathf.sin(angle), 0, 1, 0, - -Mathf.sin(angle), 0, Mathf.cos(angle) - ); - return m; - } + public Matrix3f getRotationMatrix(float angle) { + Matrix3f m = new Matrix3f(Mathf.cos(angle), 0, Mathf.sin(angle), 0, 1, 0, + -Mathf.sin(angle), 0, Mathf.cos(angle)); + return m; + } - public boolean isEnabled() { - return enabled; - } + public boolean isEnabled() { + return enabled; + } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index a79199ac..4d871a78 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -9,150 +9,186 @@ public class GraphicsPImpl implements Graphics { - private Color color; - private PGraphics g; - private Mesh3DRenderer renderer; - - public GraphicsPImpl(PApplet p) { - this.g = p.g; - renderer = new Mesh3DRenderer(p); - } - - @Override - public void fillFaces(Mesh3D mesh) { - g.noStroke(); - fill(); - renderer.drawFaces(mesh); - } - - @Override - public int getWidth() { - return g.width; - } - - @Override - public int getHeight() { - return g.height; - } - - private void stroke() { - g.stroke( - color.getRed(), color.getGreen(), color.getBlue(), - color.getAlpha() - ); - } - - private void fill() { - g.fill( - color.getRed(), color.getGreen(), color.getBlue(), - color.getAlpha() - ); - } - - @Override - public void pushMatrix() { - g.pushMatrix(); - } - - @Override - public void popMatrix() { - g.popMatrix(); - } - - @Override - public void translate(float x, float y) { - g.translate(x, y); - } - - @Override - public void strokeWeight(float weight) { - g.strokeWeight(weight); - } - - @Override - public void setColor(Color color) { - this.color = color; - } - - @Override - public void setColor(int red, int green, int blue) { - color = new Color(red, green, blue); - } - - @Override - public void drawRect(float x, float y, float width, float height) { - g.pushStyle(); - g.noFill(); - stroke(); - g.rectMode(PApplet.CORNER); - g.rect(x, y, width, height); - g.popStyle(); - } - - @Override - public void fillRect(float x, float y, float width, float height) { - g.pushStyle(); - g.noStroke(); - fill(); - g.rectMode(PApplet.CORNER); - g.rect(x, y, width, height); - g.popStyle(); - } - - @Override - public void textSize(float size) { - g.textSize(size); - } - - @Override - public float getTextSize() { - return g.textSize; - } - - @Override - public float textWidth(String text) { - return g.textWidth(text); - } - - @Override - public float textAscent() { - return g.textAscent(); - } - - @Override - public float textDescent() { - return g.textDescent(); - } - - @Override - public void text(String text, float x, float y) { - fill(); - g.text(text, x, y); - } - - @Override - public void enableDepthTest() { - g.hint(PApplet.ENABLE_DEPTH_TEST); - } - - @Override - public void disableDepthTest() { - g.hint(PApplet.DISABLE_DEPTH_TEST); - } - - @Override - public void rotateX(float angle) { - g.rotateX(angle); - } - - @Override - public void rotateY(float angle) { - g.rotateY(angle); - } - - @Override - public void rotateZ(float angle) { - g.rotate(angle); - } + private Color color; + + private PGraphics g; + + private Mesh3DRenderer renderer; + + public GraphicsPImpl(PApplet p) { + this.g = p.g; + renderer = new Mesh3DRenderer(p); + } + + @Override + public void fillFaces(Mesh3D mesh) { + g.noStroke(); + fill(); + renderer.drawFaces(mesh); + } + + @Override + public int getWidth() { + return g.width; + } + + @Override + public int getHeight() { + return g.height; + } + + private void stroke() { + g.stroke(color.getRed(), color.getGreen(), color.getBlue(), + color.getAlpha()); + } + + private void fill() { + g.fill(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + } + + @Override + public void pushMatrix() { + g.pushMatrix(); + } + + @Override + public void popMatrix() { + g.popMatrix(); + } + + @Override + public void translate(float x, float y) { + g.translate(x, y); + } + + @Override + public void strokeWeight(float weight) { + g.strokeWeight(weight); + } + + @Override + public void setColor(Color color) { + this.color = color; + } + + @Override + public void setColor(int red, int green, int blue) { + color = new Color(red, green, blue); + } + + @Override + public void setColor(math.Color color) { + setColor(color.getRedInt(), color.getGreenInt(), color.getBlueInt()); + } + + @Override + public void drawRect(float x, float y, float width, float height) { + g.pushStyle(); + g.noFill(); + stroke(); + g.rectMode(PApplet.CORNER); + g.rect(x, y, width, height); + g.popStyle(); + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2) { + g.pushStyle(); + g.noFill(); + stroke(); + g.line(x1, y1, x2, y2); + g.popStyle(); + } + + @Override + public void fillRect(float x, float y, float width, float height) { + g.pushStyle(); + g.noStroke(); + fill(); + g.rectMode(PApplet.CORNER); + g.rect(x, y, width, height); + g.popStyle(); + } + + @Override + public void drawOval(float x, float y, float width, float height) { + g.pushStyle(); + g.noFill(); + stroke(); + g.ellipseMode(PApplet.CORNER); + g.ellipse(x, y, height, width); + g.popStyle(); + } + + @Override + public void fillOval(float x, float y, float width, float height) { + g.pushStyle(); + g.noStroke(); + fill(); + g.ellipseMode(PApplet.CORNER); + g.ellipse(x, y, height, width); + g.popStyle(); + } + + @Override + public void textSize(float size) { + g.textSize(size); + } + + @Override + public float getTextSize() { + return g.textSize; + } + + @Override + public float textWidth(String text) { + return g.textWidth(text); + } + + @Override + public float textAscent() { + return g.textAscent(); + } + + @Override + public float textDescent() { + return g.textDescent(); + } + + @Override + public void text(String text, float x, float y) { + fill(); + g.text(text, x, y); + } + + @Override + public void enableDepthTest() { + g.hint(PApplet.ENABLE_DEPTH_TEST); + } + + @Override + public void disableDepthTest() { + g.hint(PApplet.DISABLE_DEPTH_TEST); + } + + @Override + public void rotate(float angle) { + g.rotate(angle); + } + + @Override + public void rotateX(float angle) { + g.rotateX(angle); + } + + @Override + public void rotateY(float angle) { + g.rotateY(angle); + } + + @Override + public void rotateZ(float angle) { + g.rotate(angle); + } } diff --git a/src/main/java/workspace/ModelListener.java b/src/main/java/workspace/ModelListener.java index 74dd7c5d..c4255196 100644 --- a/src/main/java/workspace/ModelListener.java +++ b/src/main/java/workspace/ModelListener.java @@ -2,6 +2,6 @@ public interface ModelListener { - void onModelChanged(); + void onModelChanged(); } diff --git a/src/main/java/workspace/SceneObject.java b/src/main/java/workspace/SceneObject.java index 67cc9d64..ee3775a3 100644 --- a/src/main/java/workspace/SceneObject.java +++ b/src/main/java/workspace/SceneObject.java @@ -5,47 +5,47 @@ public class SceneObject { - private String name; + private String name; - private Color fillColor; + private Color fillColor; - private Mesh3D mesh; + private Mesh3D mesh; - public SceneObject(Mesh3D mesh) { - this.mesh = mesh; - fillColor = Color.WHITE; - } + public SceneObject(Mesh3D mesh) { + this.mesh = mesh; + fillColor = Color.WHITE; + } - public SceneObject() { - this(null); - } + public SceneObject() { + this(null); + } - public Mesh3D getMesh() { - return mesh; - } + public Mesh3D getMesh() { + return mesh; + } - public void setMesh(Mesh3D mesh) { - this.mesh = mesh; - } + public void setMesh(Mesh3D mesh) { + this.mesh = mesh; + } - public Color getFillColor() { - return fillColor; - } + public Color getFillColor() { + return fillColor; + } - public void setFillColor(Color fillColor) { - this.fillColor = fillColor; - } + public void setFillColor(Color fillColor) { + this.fillColor = fillColor; + } - public void setFillColor(int r, int g, int b) { - fillColor = new Color(r, g, b); - } + public void setFillColor(int r, int g, int b) { + fillColor = new Color(r, g, b); + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } } diff --git a/src/main/java/workspace/Workspace.java b/src/main/java/workspace/Workspace.java index 41b49c70..075f248d 100644 --- a/src/main/java/workspace/Workspace.java +++ b/src/main/java/workspace/Workspace.java @@ -1,5 +1,6 @@ package workspace; +import math.Vector3f; import mesh.Mesh3D; import mesh.util.VertexNormals; import processing.core.PApplet; @@ -14,505 +15,509 @@ public class Workspace extends Editor implements ModelListener { - int vertices; - - int faces; - - private PApplet p; - - private Mesh3DRenderer renderer; - - private FirstPersonView firstPersonView; - - private ObjectSelectionRender selectionRender; - - private SceneObject selectedObject; - - private boolean select; - - private GraphicsPImpl gImpl; - - public Workspace(PApplet p) { - this.p = p; - registerMethods(); - firstPersonView = new FirstPersonView(p); - renderer = new Mesh3DRenderer(p); - selectionRender = new ObjectSelectionRender(p); - refreshLoopPreference(); - model.addListener(this); - gImpl = new GraphicsPImpl(p); - } - - private void registerMethods() { - p.registerMethod("pre", this); - p.registerMethod("draw", this); - p.registerMethod("post", this); - p.registerMethod("mouseEvent", this); - p.registerMethod("keyEvent", this); - } - - @Override - public void onModelChanged() { - super.onModelChanged(); - refreshLoopPreference(); - gizmo.setRotationX(model.getRotationX()); - gizmo.setRotationY(model.getRotationY()); - gizmo.setRotationZ(model.getRotationZ()); - if (!isLoop()) - p.redraw(); - } - - protected void refreshLoopPreference() { - if (!isLoop()) { - p.noLoop(); - } else { - p.loop(); - } - } - - public void applyTransformations() { - if (firstPersonView.isEnabled()) { - firstPersonView.apply(); - p.scale(getScale()); - } else { - p.translate(p.width / 2, p.height / 2); - p.translate(getPanningX(), getPanningY()); - p.scale(getScale()); - p.rotateX(getRotationX()); - p.rotateY(getRotationY()); - p.rotateZ(getRotationZ()); - } - } - - public void applyCamera() { - firstPersonView.apply(); - } - - public void drawGrid(int rows, int cols, float size) { - if (!isGridVisible()) - return; - - p.stroke(UiValues.getColor(UiConstants.KEY_GRID_COLOR).getRGBA()); - p.noFill(); - - p.pushMatrix(); - p.rotateX(PApplet.radians(-90)); - p.translate(-cols / 2, -rows / 2); - - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - p.rect(j * size, i * size, size, size); - } - } - - p.popMatrix(); - } - - protected void drawAxis(float size) { - p.pushStyle(); - p.pushMatrix(); - - p.noFill(); - p.strokeWeight(1.5f / getScale()); - - if (isxAxisVisible()) { - p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_X_COLOR).getRGBA()); - p.line(size, 0, 0, 0, 0, 0); - p.line(-size, 0, 0, 0, 0, 0); - } - - if (isyAxisVisible()) { - p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_Y_COLOR).getRGBA()); - p.line(0, size, 0, 0, 0, 0); - p.line(0, -size, 0, 0, 0, 0); - } - - if (iszAxisVisible()) { - p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_Z_COLOR).getRGBA()); - p.line(0, 0, size, 0, 0, 0); - p.line(0, 0, -size, 0, 0, 0); - } - - p.popStyle(); - p.popMatrix(); - } - - public void pre() { - resize(0, 0, p.width, p.height); - vertices = 0; - faces = 0; - p.background(getBackground().getRGBA()); - p.lights(); - applyTransformations(); - p.strokeWeight(1 / getScale()); - drawGrid(32, 32, 1); - drawAxis(2000); - } - - protected void disableDepthTestFor2dDrawing() { - p.hint(PApplet.DISABLE_DEPTH_TEST); - } - - protected void enableDepthTestFor3dDrawing() { - p.hint(PApplet.ENABLE_DEPTH_TEST); - } - - protected void drawUI() { - disableDepthTestFor2dDrawing(); - p.camera(); - p.noLights(); - rootUi.draw(gImpl); - enableDepthTestFor3dDrawing(); - } - - public void draw() { - drawSelection(); - drawSceneObjects(); - - if (selectedObject != null) { - p.fill(255); - renderer.drawFaces(selectedObject.getMesh()); - } - - drawUI(); - - menu.setText(getInformationString()); - - // Debug code + int vertices; + + int faces; + + private PApplet p; + + private Mesh3DRenderer renderer; + + private FirstPersonView firstPersonView; + + private ObjectSelectionRender selectionRender; + + private SceneObject selectedObject; + + private boolean select; + + private GraphicsPImpl gImpl; + + public Workspace(PApplet p) { + this.p = p; + registerMethods(); + firstPersonView = new FirstPersonView(p); + renderer = new Mesh3DRenderer(p); + selectionRender = new ObjectSelectionRender(p); + refreshLoopPreference(); + model.addListener(this); + gImpl = new GraphicsPImpl(p); + } + + private void registerMethods() { + p.registerMethod("pre", this); + p.registerMethod("draw", this); + p.registerMethod("post", this); + p.registerMethod("mouseEvent", this); + p.registerMethod("keyEvent", this); + } + + @Override + public void onModelChanged() { + super.onModelChanged(); + refreshLoopPreference(); + gizmo.setRotation(new Vector3f(model.getRotationX(), model.getRotationY(), + model.getRotationZ())); + if (!isLoop()) + p.redraw(); + } + + protected void refreshLoopPreference() { + if (!isLoop()) { + p.noLoop(); + } else { + p.loop(); + } + } + + public void applyTransformations() { + if (firstPersonView.isEnabled()) { + firstPersonView.apply(); + p.scale(getScale()); + } else { + p.translate(p.width / 2, p.height / 2); + p.translate(getPanningX(), getPanningY()); + p.scale(getScale()); + p.rotateX(getRotationX()); + p.rotateY(getRotationY()); + p.rotateZ(getRotationZ()); + } + } + + public void applyCamera() { + firstPersonView.apply(); + } + + public void drawGrid(int rows, int cols, float size) { + if (!isGridVisible()) + return; + + p.stroke(UiValues.getColor(UiConstants.KEY_GRID_COLOR).getRGBA()); + p.noFill(); + + p.pushMatrix(); + p.rotateX(PApplet.radians(-90)); + p.translate(-cols / 2, -rows / 2); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + p.rect(j * size, i * size, size, size); + } + } + + p.popMatrix(); + } + + protected void drawAxis(float size) { + p.pushStyle(); + p.pushMatrix(); + + p.noFill(); + p.strokeWeight(1.5f / getScale()); + + if (isxAxisVisible()) { + p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_X_COLOR).getRGBA()); + p.line(size, 0, 0, 0, 0, 0); + p.line(-size, 0, 0, 0, 0, 0); + } + + if (isyAxisVisible()) { + p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_Y_COLOR).getRGBA()); + p.line(0, size, 0, 0, 0, 0); + p.line(0, -size, 0, 0, 0, 0); + } + + if (iszAxisVisible()) { + p.stroke(UiValues.getColor(UiConstants.KEY_AXIS_Z_COLOR).getRGBA()); + p.line(0, 0, size, 0, 0, 0); + p.line(0, 0, -size, 0, 0, 0); + } + + p.popStyle(); + p.popMatrix(); + } + + public void pre() { + resize(0, 0, p.width, p.height); + vertices = 0; + faces = 0; + p.background(getBackground().getRGBA()); + p.lights(); + applyTransformations(); + p.strokeWeight(1 / getScale()); + drawGrid(32, 32, 1); + drawAxis(2000); + } + + protected void disableDepthTestFor2dDrawing() { + p.hint(PApplet.DISABLE_DEPTH_TEST); + } + + protected void enableDepthTestFor3dDrawing() { + p.hint(PApplet.ENABLE_DEPTH_TEST); + } + + protected void drawUI() { + disableDepthTestFor2dDrawing(); + p.camera(); + p.noLights(); + rootUi.render(gImpl); + enableDepthTestFor3dDrawing(); + } + + public void draw() { + drawSelection(); + drawSceneObjects(); + + if (selectedObject != null) { + p.fill(255); + renderer.drawFaces(selectedObject.getMesh()); + } + + drawUI(); + + menu.setText(getInformationString()); + + // Debug code // p.pushMatrix(); // p.camera(); // p.hint(PApplet.DISABLE_DEPTH_TEST); // selectionRender.drawColorBuffer(); // p.hint(PApplet.ENABLE_DEPTH_TEST); // p.popMatrix(); - } - - private void drawSelection() { - selectionRender.draw(sceneObjects); - } - - public void drawSceneObjects() { - for (SceneObject sceneObject : sceneObjects) { - draw(sceneObject.getMesh(), sceneObject.getFillColor()); - } - } - - public void draw(Mesh3D mesh, Color color) { - p.pushStyle(); - vertices = mesh.vertices.size(); - faces = mesh.faces.size(); - - if (!isWireframe()) { - p.noStroke(); - p.fill( - color.getRed(), color.getGreen(), color.getBlue(), - color.getAlpha() - ); - renderer.drawFaces(mesh, mesh.faces, getShading()); - p.stroke(0); - } else { - p.stroke( - UiValues.getColor(UiConstants.KEY_EDITOR_WIREFRAME_COLOR) - .getRGBA() - ); - renderer.drawEdges(mesh); - } - - if (isEdgesVisible()) - renderer.drawEdges(mesh); - - if (isFaceNormalsVisible()) { - p.stroke(255); - renderer.drawFaceNormals(mesh); - } - - if (isVertexNormalsVisible()) { - p.stroke(35, 97, 221); - VertexNormals normals = new VertexNormals(mesh); - renderer.drawVertexNormals(mesh, normals.getVertexNormals()); - } - p.popStyle(); - } - - public void drawVertices(Mesh3D mesh) { - p.pushStyle(); - p.stroke(255); - p.fill(255); - p.strokeWeight(0.08f); - renderer.drawVertices(mesh); - p.popStyle(); - } - - public void draw(Mesh3D mesh) { - draw(mesh, new Color(220, 220, 220)); - } - - public void post() { + } + + private void drawSelection() { + selectionRender.draw(sceneObjects); + } + + public void drawSceneObjects() { + for (SceneObject sceneObject : sceneObjects) { + draw(sceneObject.getMesh(), sceneObject.getFillColor()); + } + } + + public void draw(Mesh3D mesh, Color color) { + p.pushStyle(); + vertices = mesh.vertices.size(); + faces = mesh.faces.size(); + + if (!isWireframe()) { + if (isEdgesVisible()) { + p.stroke(0); + } else { + p.noStroke(); + } + p.fill(color.getRed(), color.getGreen(), color.getBlue(), + color.getAlpha()); + renderer.drawFaces(mesh, mesh.faces, getShading()); + } else { + p.stroke( + UiValues.getColor(UiConstants.KEY_EDITOR_WIREFRAME_COLOR).getRGBA()); + renderer.drawEdges(mesh); + } + +// if (isEdgesVisible()) { +// p.noFill(); +// renderer.drawEdges(mesh); +// } + + if (isFaceNormalsVisible()) { + p.stroke(255); + renderer.drawFaceNormals(mesh); + } + + if (isVertexNormalsVisible()) { + p.stroke(35, 97, 221); + VertexNormals normals = new VertexNormals(mesh); + renderer.drawVertexNormals(mesh, normals.getVertexNormals()); + } + p.popStyle(); + } + + public void drawVertices(Mesh3D mesh) { + p.pushStyle(); + p.stroke(255); + p.fill(255); + p.strokeWeight(0.08f); + renderer.drawVertices(mesh); + p.popStyle(); + } + + public void draw(Mesh3D mesh) { + draw(mesh, new Color(220, 220, 220)); + } + + public void post() { // p.saveFrame("output/workspace/workspace_demo####.png"); - } - - protected void onMouseDragged() { - if (p.mouseButton != 3) - return; - float rx = getRotationX() - + (p.pmouseY - p.mouseY) * PApplet.TWO_PI / 1000; - float ry = getRotationY() - - (p.pmouseX - p.mouseX) * PApplet.TWO_PI / 1000; - setRotation(rx, ry, 0); - } - - protected void onShiftMouseDragged() { - if (p.mouseButton != 3) - return; - float panningX = getPanningX() - ((p.pmouseX - p.mouseX) * 2); - float panningY = getPanningY() - ((p.pmouseY - p.mouseY) * 2); - setPanningX(panningX); - setPanningY(panningY); - } - - private void handleSelection(int x, int y) { - SceneObject sceneObject = null; - String sceneObjectName = selectionRender.getObject(x, y); - - if (sceneObjectName != null) { - for (SceneObject o : sceneObjects) { - if (o.getName().equals(sceneObjectName)) { - sceneObject = o; - break; - } - } - } - selectedObject = sceneObject; - } - - /** - * - * @param e - */ - public void mouseEvent(MouseEvent e) { - int action = e.getAction(); - - switch (action) { - case MouseEvent.CLICK: - select = true; - handleMouseClicked(e.getX(), e.getY()); - break; - case MouseEvent.DRAG: - handleMouseDragged(e.getX(), e.getY()); - if (e.isShiftDown()) { - onShiftMouseDragged(); - break; - } - onMouseDragged(); - break; - case MouseEvent.WHEEL: - handleMouseWheel(e.getCount()); - break; - } - // Model? - if (!isLoop()) - p.redraw(); - } - - public void keyEvent(KeyEvent e) { - if (!isUseKeyBindings()) - return; - - if (e.getAction() != KeyEvent.TYPE) - return; - - switch (e.getKey()) { - case '4': - if (!firstPersonView.isEnabled()) { - setLoop(true); - } else { - p.redraw(); - } - firstPersonView.setEnabled(!firstPersonView.isEnabled()); - commands.getCommand('s').setEnabled(!firstPersonView.isEnabled()); - break; - default: - commands.execute(e.getKey()); - break; - } - } - - protected String getInformationString() { - StringBuffer buffer = new StringBuffer(); - buffer.append("Verts:"); - buffer.append(vertices); - buffer.append(" | Faces:"); - buffer.append(faces); - buffer.append(" | FPS:"); - buffer.append(p.frameRate); - buffer.append(" | FrameCount:"); - buffer.append(p.frameCount); - return buffer.toString(); - } - - public Mesh3DRenderer getRenderer() { - return renderer; - } - - public SceneObject getSceneObject(int mouseX, int mouseY) { - String objectName = selectionRender.getObject(mouseX, mouseY); - - if (objectName == null) - return null; - for (SceneObject sceneObject : sceneObjects) { - if (sceneObject.getName().equals(objectName)) { - return sceneObject; - } - } - return null; - } - - public float getPanningX() { - return model.getPanningX(); - } - - public void setPanningX(float panningX) { - model.setPanningX(panningX); - } - - public float getPanningY() { - return model.getPanningY(); - } - - public void setPanningY(float panningY) { - model.setPanningY(panningY); - } - - public float getRotationX() { - return model.getRotationX(); - } - - public float getRotationY() { - return model.getRotationY(); - } - - public float getRotationZ() { - return model.getRotationZ(); - } - - public void setRotation(float rx, float ry, float rz) { - model.setRotation(rx, ry, rz); - } - - public float getScale() { - return model.getScale(); - } - - public void setScale(float scale) { - model.setScale(scale); - } - - public WorkspaceModel getModel() { - return model; - } - - public boolean isxAxisVisible() { - return model.isxAxisVisible(); - } - - public void setxAxisVisible(boolean xAxisVisible) { - model.setxAxisVisible(xAxisVisible); - } - - public boolean isyAxisVisible() { - return model.isyAxisVisible(); - } - - public void setyAxisVisible(boolean yAxisVisible) { - model.setyAxisVisible(yAxisVisible); - } - - public boolean iszAxisVisible() { - return model.iszAxisVisible(); - } - - public void setzAxisVisible(boolean zAxisVisible) { - model.setzAxisVisible(zAxisVisible); - } - - public boolean isGridVisible() { - return model.isGridVisible(); - } - - public void setGridVisible(boolean gridVisible) { - model.setGridVisible(gridVisible); - } - - public boolean isFaceNormalsVisible() { - return model.isFaceNormalsVisible(); - } - - public void setFaceNormalsVisible(boolean faceNormalsVisible) { - model.setFaceNormalsVisible(faceNormalsVisible); - } - - public boolean isVertexNormalsVisible() { - return model.isVertexNormalsVisible(); - } - - public void setVertexNormalsVisible(boolean vertexNormalsVisible) { - model.setVertexNormalsVisible(vertexNormalsVisible); - } - - public boolean isEdgesVisible() { - return model.isEdgesVisible(); - } - - public void setEdgesVisible(boolean edgesVisible) { - model.setEdgesVisible(edgesVisible); - } + } + + protected void onMouseDragged() { + if (p.mouseButton != 3) + return; + float rx = getRotationX() + (p.pmouseY - p.mouseY) * PApplet.TWO_PI / 1000; + float ry = getRotationY() - (p.pmouseX - p.mouseX) * PApplet.TWO_PI / 1000; + setRotation(rx, ry, 0); + } + + protected void onShiftMouseDragged() { + if (p.mouseButton != 3) + return; + float panningX = getPanningX() - ((p.pmouseX - p.mouseX) * 2); + float panningY = getPanningY() - ((p.pmouseY - p.mouseY) * 2); + setPanningX(panningX); + setPanningY(panningY); + } + + private void handleSelection(int x, int y) { + SceneObject sceneObject = null; + String sceneObjectName = selectionRender.getObject(x, y); + + if (sceneObjectName != null) { + for (SceneObject o : sceneObjects) { + if (o.getName().equals(sceneObjectName)) { + sceneObject = o; + break; + } + } + } + selectedObject = sceneObject; + } + + /** + * + * @param e + */ + public void mouseEvent(MouseEvent e) { + int action = e.getAction(); + + switch (action) { + case MouseEvent.CLICK: + select = true; + handleMouseClicked(e.getX(), e.getY()); + break; + case MouseEvent.DRAG: + handleMouseDragged(e.getX(), e.getY()); + if (e.isShiftDown()) { + onShiftMouseDragged(); + break; + } + onMouseDragged(); + break; + case MouseEvent.WHEEL: + handleMouseWheel(e.getCount()); + break; + case MouseEvent.RELEASE: + handleMouseReleased(e.getX(), e.getY()); + break; + case MouseEvent.PRESS: + handleMousePressed(e.getX(), e.getY()); + break; + } + // Model? + if (!isLoop()) + p.redraw(); + } + + public void keyEvent(KeyEvent e) { + if (!isUseKeyBindings()) + return; + + if (e.getAction() != KeyEvent.TYPE) + return; + + switch (e.getKey()) { + case '4': + if (!firstPersonView.isEnabled()) { + setLoop(true); + } else { + p.redraw(); + } + firstPersonView.setEnabled(!firstPersonView.isEnabled()); + commands.getCommand('s').setEnabled(!firstPersonView.isEnabled()); + break; + default: + commands.execute(e.getKey()); + break; + } + } + + protected String getInformationString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("Verts:"); + buffer.append(vertices); + buffer.append(" | Faces:"); + buffer.append(faces); + buffer.append(" | FPS:"); + buffer.append(p.frameRate); + buffer.append(" | FrameCount:"); + buffer.append(p.frameCount); + return buffer.toString(); + } + + public Mesh3DRenderer getRenderer() { + return renderer; + } + + public SceneObject getSceneObject(int mouseX, int mouseY) { + String objectName = selectionRender.getObject(mouseX, mouseY); + + if (objectName == null) + return null; + for (SceneObject sceneObject : sceneObjects) { + if (sceneObject.getName().equals(objectName)) { + return sceneObject; + } + } + return null; + } + + public float getPanningX() { + return model.getPanningX(); + } + + public void setPanningX(float panningX) { + model.setPanningX(panningX); + } + + public float getPanningY() { + return model.getPanningY(); + } + + public void setPanningY(float panningY) { + model.setPanningY(panningY); + } + + public float getRotationX() { + return model.getRotationX(); + } + + public float getRotationY() { + return model.getRotationY(); + } + + public float getRotationZ() { + return model.getRotationZ(); + } + + public void setRotation(float rx, float ry, float rz) { + model.setRotation(rx, ry, rz); + } + + public float getScale() { + return model.getScale(); + } + + public void setScale(float scale) { + model.setScale(scale); + } + + public WorkspaceModel getModel() { + return model; + } + + public boolean isxAxisVisible() { + return model.isxAxisVisible(); + } + + public void setxAxisVisible(boolean xAxisVisible) { + model.setxAxisVisible(xAxisVisible); + } + + public boolean isyAxisVisible() { + return model.isyAxisVisible(); + } + + public void setyAxisVisible(boolean yAxisVisible) { + model.setyAxisVisible(yAxisVisible); + } + + public boolean iszAxisVisible() { + return model.iszAxisVisible(); + } + + public void setzAxisVisible(boolean zAxisVisible) { + model.setzAxisVisible(zAxisVisible); + } + + public boolean isGridVisible() { + return model.isGridVisible(); + } + + public void setGridVisible(boolean gridVisible) { + model.setGridVisible(gridVisible); + } + + public boolean isFaceNormalsVisible() { + return model.isFaceNormalsVisible(); + } + + public void setFaceNormalsVisible(boolean faceNormalsVisible) { + model.setFaceNormalsVisible(faceNormalsVisible); + } + + public boolean isVertexNormalsVisible() { + return model.isVertexNormalsVisible(); + } + + public void setVertexNormalsVisible(boolean vertexNormalsVisible) { + model.setVertexNormalsVisible(vertexNormalsVisible); + } + + public boolean isEdgesVisible() { + return model.isEdgesVisible(); + } + + public void setEdgesVisible(boolean edgesVisible) { + model.setEdgesVisible(edgesVisible); + } - public boolean isUiVisible() { - return model.isUiVisible(); - } + public boolean isUiVisible() { + return model.isUiVisible(); + } - public void setUiVisible(boolean uiVisible) { - model.setUiVisible(uiVisible); - } + public void setUiVisible(boolean uiVisible) { + model.setUiVisible(uiVisible); + } - public boolean isWireframe() { - return model.isWireframe(); - } + public boolean isWireframe() { + return model.isWireframe(); + } - public void setWireframe(boolean wireframe) { - model.setWireframe(wireframe); - } + public void setWireframe(boolean wireframe) { + model.setWireframe(wireframe); + } - public boolean isLoop() { - return model.isLoop(); - } + public boolean isLoop() { + return model.isLoop(); + } - public void setLoop(boolean loop) { - model.setLoop(loop); - } + public void setLoop(boolean loop) { + model.setLoop(loop); + } - public boolean isUseKeyBindings() { - return model.isUseKeyBindings(); - } + public boolean isUseKeyBindings() { + return model.isUseKeyBindings(); + } - public void setUseKeyBindings(boolean useKeyBindings) { - model.setUseKeyBindings(useKeyBindings); - } + public void setUseKeyBindings(boolean useKeyBindings) { + model.setUseKeyBindings(useKeyBindings); + } - public Color getBackground() { - return model.getBackground(); - } + public Color getBackground() { + return model.getBackground(); + } - public void setBackground(Color background) { - model.setBackground(background); - } + public void setBackground(Color background) { + model.setBackground(background); + } - public Shading getShading() { - return model.getShading(); - } + public Shading getShading() { + return model.getShading(); + } - public void setShading(Shading shading) { - model.setShading(shading); - } + public void setShading(Shading shading) { + model.setShading(shading); + } } diff --git a/src/main/java/workspace/WorkspaceModel.java b/src/main/java/workspace/WorkspaceModel.java index c4d9387d..58acd851 100644 --- a/src/main/java/workspace/WorkspaceModel.java +++ b/src/main/java/workspace/WorkspaceModel.java @@ -11,323 +11,323 @@ public class WorkspaceModel { - private float panningX; + private float panningX; - private float panningY; + private float panningY; - private float rotationX; + private float rotationX; - private float rotationY; + private float rotationY; - private float rotationZ; + private float rotationZ; - private float scale; + private float scale; - private float minScale; + private float minScale; - private float maxScale; + private float maxScale; - private boolean xAxisVisible; + private boolean xAxisVisible; - private boolean yAxisVisible; + private boolean yAxisVisible; - private boolean zAxisVisible; + private boolean zAxisVisible; - private boolean gridVisible; + private boolean gridVisible; - private boolean faceNormalsVisible; + private boolean faceNormalsVisible; - private boolean vertexNormalsVisible; + private boolean vertexNormalsVisible; - private boolean edgesVisible; + private boolean edgesVisible; - private boolean uiVisible; + private boolean uiVisible; - private boolean wireframe; + private boolean wireframe; - private boolean loop; + private boolean loop; - private boolean useKeyBindings; + private boolean useKeyBindings; - private Shading shading; + private Shading shading; - private Color background; + private Color background; - private List listeners; + private List listeners; - public WorkspaceModel() { - scale = 100; - minScale = 1; - maxScale = 1000; - rotationX = Mathf.toRadians(-30); - rotationY = Mathf.toRadians(30); - xAxisVisible = false; - yAxisVisible = false; - zAxisVisible = false; - gridVisible = false; - faceNormalsVisible = false; - vertexNormalsVisible = false; - edgesVisible = true; - uiVisible = true; - wireframe = false; - loop = false; - useKeyBindings = true; - shading = Shading.FLAT; - background = UiValues.getColor(UiConstants.KEY_EDITOR_BACKGROUND_COLOR); - listeners = new ArrayList(); - } - - public float getPanningX() { - return panningX; - } - - public void setPanningX(float panningX) { - if (this.panningX == panningX) - return; - this.panningX = panningX; - fireChangeEvent(); - } - - public float getPanningY() { - return panningY; - } - - public void setPanningY(float panningY) { - if (this.panningY == panningY) - return; - this.panningY = panningY; - fireChangeEvent(); - } - - public float getRotationX() { - return rotationX; - } - - public void setRotationX(float rotationX) { - if (this.rotationX == rotationX) - return; - this.rotationX = rotationX; - fireChangeEvent(); - } - - public float getRotationY() { - return rotationY; - } - - public void setRotationY(float rotationY) { - if (this.rotationY == rotationY) - return; - this.rotationY = rotationY; - fireChangeEvent(); - } - - public float getRotationZ() { - return rotationZ; - } - - public void setRotationZ(float rotationZ) { - if (this.rotationZ == rotationZ) - return; - this.rotationZ = rotationZ; - fireChangeEvent(); - } - - public void setRotation(float rx, float ry, float rz) { - if (rotationX == rx && rotationY == ry && rotationZ == rz) - return; - rotationX = rx; - rotationY = ry; - rotationZ = rz; - fireChangeEvent(); - } - - public float getScale() { - return scale; - } - - public void setScale(float scale) { - scale = Mathf.clamp(scale, getMinScale(), getMaxScale()); - if (this.scale == scale) - return; - this.scale = scale; - fireChangeEvent(); - } - - public float getMinScale() { - return minScale; - } - - public void setMinScale(float minScale) { - if (this.minScale == minScale) - return; - this.minScale = minScale; - fireChangeEvent(); - } - - public float getMaxScale() { - return maxScale; - } - - public void setMaxScale(float maxScale) { - if (this.maxScale == maxScale) - return; - this.maxScale = maxScale; - fireChangeEvent(); - } - - public boolean isxAxisVisible() { - return xAxisVisible; - } - - public void setxAxisVisible(boolean xAxisVisible) { - if (this.xAxisVisible == xAxisVisible) - return; - this.xAxisVisible = xAxisVisible; - fireChangeEvent(); - } - - public boolean isyAxisVisible() { - return yAxisVisible; - } - - public void setyAxisVisible(boolean yAxisVisible) { - if (this.yAxisVisible == yAxisVisible) - return; - this.yAxisVisible = yAxisVisible; - fireChangeEvent(); - } - - public boolean iszAxisVisible() { - return zAxisVisible; - } - - public void setzAxisVisible(boolean zAxisVisible) { - if (this.zAxisVisible == zAxisVisible) - return; - this.zAxisVisible = zAxisVisible; - fireChangeEvent(); - } - - public boolean isGridVisible() { - return gridVisible; - } - - public void setGridVisible(boolean gridVisible) { - if (this.gridVisible == gridVisible) - return; - this.gridVisible = gridVisible; - fireChangeEvent(); - } - - public boolean isFaceNormalsVisible() { - return faceNormalsVisible; - } - - public void setFaceNormalsVisible(boolean faceNormalsVisible) { - if (this.faceNormalsVisible == faceNormalsVisible) - return; - this.faceNormalsVisible = faceNormalsVisible; - fireChangeEvent(); - } - - public boolean isVertexNormalsVisible() { - return vertexNormalsVisible; - } - - public void setVertexNormalsVisible(boolean vertexNormalsVisible) { - if (this.vertexNormalsVisible == vertexNormalsVisible) - return; - this.vertexNormalsVisible = vertexNormalsVisible; - fireChangeEvent(); - } - - public boolean isEdgesVisible() { - return edgesVisible; - } - - public void setEdgesVisible(boolean edgesVisible) { - if (this.edgesVisible == edgesVisible) - return; - this.edgesVisible = edgesVisible; - fireChangeEvent(); - } - - public boolean isUiVisible() { - return uiVisible; - } - - public void setUiVisible(boolean uiVisible) { - if (this.uiVisible == uiVisible) - return; - this.uiVisible = uiVisible; - fireChangeEvent(); - } - - public boolean isWireframe() { - return wireframe; - } - - public void setWireframe(boolean wireframe) { - if (this.wireframe == wireframe) - return; - this.wireframe = wireframe; - fireChangeEvent(); - } - - public boolean isLoop() { - return loop; - } - - public void setLoop(boolean loop) { - if (this.loop == loop) - return; - this.loop = loop; - fireChangeEvent(); - } - - public Shading getShading() { - return shading; - } - - public void setShading(Shading shading) { - if (this.shading == shading) - return; - this.shading = shading; - fireChangeEvent(); - } - - public Color getBackground() { - return background; - } - - public void setBackground(Color background) { - if (this.background.equals(background)) - return; - this.background = background; - fireChangeEvent(); - } - - public boolean isUseKeyBindings() { - return useKeyBindings; - } - - public void setUseKeyBindings(boolean useKeyBindings) { - if (this.useKeyBindings == useKeyBindings) - return; - this.useKeyBindings = useKeyBindings; - fireChangeEvent(); - } - - public void fireChangeEvent() { - for (ModelListener l : listeners) { - l.onModelChanged(); - } - } - - public void addListener(ModelListener listener) { - if (listener == null) - return; - listeners.add(listener); - } + public WorkspaceModel() { + scale = 100; + minScale = 1; + maxScale = 1000; + rotationX = Mathf.toRadians(-30); + rotationY = Mathf.toRadians(30); + xAxisVisible = false; + yAxisVisible = false; + zAxisVisible = false; + gridVisible = false; + faceNormalsVisible = false; + vertexNormalsVisible = false; + edgesVisible = true; + uiVisible = true; + wireframe = false; + loop = false; + useKeyBindings = true; + shading = Shading.FLAT; + background = UiValues.getColor(UiConstants.KEY_EDITOR_BACKGROUND_COLOR); + listeners = new ArrayList(); + } + + public float getPanningX() { + return panningX; + } + + public void setPanningX(float panningX) { + if (this.panningX == panningX) + return; + this.panningX = panningX; + fireChangeEvent(); + } + + public float getPanningY() { + return panningY; + } + + public void setPanningY(float panningY) { + if (this.panningY == panningY) + return; + this.panningY = panningY; + fireChangeEvent(); + } + + public float getRotationX() { + return rotationX; + } + + public void setRotationX(float rotationX) { + if (this.rotationX == rotationX) + return; + this.rotationX = rotationX; + fireChangeEvent(); + } + + public float getRotationY() { + return rotationY; + } + + public void setRotationY(float rotationY) { + if (this.rotationY == rotationY) + return; + this.rotationY = rotationY; + fireChangeEvent(); + } + + public float getRotationZ() { + return rotationZ; + } + + public void setRotationZ(float rotationZ) { + if (this.rotationZ == rotationZ) + return; + this.rotationZ = rotationZ; + fireChangeEvent(); + } + + public void setRotation(float rx, float ry, float rz) { + if (rotationX == rx && rotationY == ry && rotationZ == rz) + return; + rotationX = rx; + rotationY = ry; + rotationZ = rz; + fireChangeEvent(); + } + + public float getScale() { + return scale; + } + + public void setScale(float scale) { + scale = Mathf.clamp(scale, getMinScale(), getMaxScale()); + if (this.scale == scale) + return; + this.scale = scale; + fireChangeEvent(); + } + + public float getMinScale() { + return minScale; + } + + public void setMinScale(float minScale) { + if (this.minScale == minScale) + return; + this.minScale = minScale; + fireChangeEvent(); + } + + public float getMaxScale() { + return maxScale; + } + + public void setMaxScale(float maxScale) { + if (this.maxScale == maxScale) + return; + this.maxScale = maxScale; + fireChangeEvent(); + } + + public boolean isxAxisVisible() { + return xAxisVisible; + } + + public void setxAxisVisible(boolean xAxisVisible) { + if (this.xAxisVisible == xAxisVisible) + return; + this.xAxisVisible = xAxisVisible; + fireChangeEvent(); + } + + public boolean isyAxisVisible() { + return yAxisVisible; + } + + public void setyAxisVisible(boolean yAxisVisible) { + if (this.yAxisVisible == yAxisVisible) + return; + this.yAxisVisible = yAxisVisible; + fireChangeEvent(); + } + + public boolean iszAxisVisible() { + return zAxisVisible; + } + + public void setzAxisVisible(boolean zAxisVisible) { + if (this.zAxisVisible == zAxisVisible) + return; + this.zAxisVisible = zAxisVisible; + fireChangeEvent(); + } + + public boolean isGridVisible() { + return gridVisible; + } + + public void setGridVisible(boolean gridVisible) { + if (this.gridVisible == gridVisible) + return; + this.gridVisible = gridVisible; + fireChangeEvent(); + } + + public boolean isFaceNormalsVisible() { + return faceNormalsVisible; + } + + public void setFaceNormalsVisible(boolean faceNormalsVisible) { + if (this.faceNormalsVisible == faceNormalsVisible) + return; + this.faceNormalsVisible = faceNormalsVisible; + fireChangeEvent(); + } + + public boolean isVertexNormalsVisible() { + return vertexNormalsVisible; + } + + public void setVertexNormalsVisible(boolean vertexNormalsVisible) { + if (this.vertexNormalsVisible == vertexNormalsVisible) + return; + this.vertexNormalsVisible = vertexNormalsVisible; + fireChangeEvent(); + } + + public boolean isEdgesVisible() { + return edgesVisible; + } + + public void setEdgesVisible(boolean edgesVisible) { + if (this.edgesVisible == edgesVisible) + return; + this.edgesVisible = edgesVisible; + fireChangeEvent(); + } + + public boolean isUiVisible() { + return uiVisible; + } + + public void setUiVisible(boolean uiVisible) { + if (this.uiVisible == uiVisible) + return; + this.uiVisible = uiVisible; + fireChangeEvent(); + } + + public boolean isWireframe() { + return wireframe; + } + + public void setWireframe(boolean wireframe) { + if (this.wireframe == wireframe) + return; + this.wireframe = wireframe; + fireChangeEvent(); + } + + public boolean isLoop() { + return loop; + } + + public void setLoop(boolean loop) { + if (this.loop == loop) + return; + this.loop = loop; + fireChangeEvent(); + } + + public Shading getShading() { + return shading; + } + + public void setShading(Shading shading) { + if (this.shading == shading) + return; + this.shading = shading; + fireChangeEvent(); + } + + public Color getBackground() { + return background; + } + + public void setBackground(Color background) { + if (this.background.equals(background)) + return; + this.background = background; + fireChangeEvent(); + } + + public boolean isUseKeyBindings() { + return useKeyBindings; + } + + public void setUseKeyBindings(boolean useKeyBindings) { + if (this.useKeyBindings == useKeyBindings) + return; + this.useKeyBindings = useKeyBindings; + fireChangeEvent(); + } + + public void fireChangeEvent() { + for (ModelListener l : listeners) { + l.onModelChanged(); + } + } + + public void addListener(ModelListener listener) { + if (listener == null) + return; + listeners.add(listener); + } } diff --git a/src/main/java/workspace/WorkspaceSideBarUi.java b/src/main/java/workspace/WorkspaceSideBarUi.java index 66435b60..0679e4be 100644 --- a/src/main/java/workspace/WorkspaceSideBarUi.java +++ b/src/main/java/workspace/WorkspaceSideBarUi.java @@ -3,11 +3,11 @@ import workspace.laf.UiValues; import workspace.render.Shading; import workspace.ui.Color; -import workspace.ui.IActionListener; -import workspace.ui.UiCheckBox; import workspace.ui.UiComponent; -import workspace.ui.UiLabel; -import workspace.ui.UiPanel; +import workspace.ui.elements.UiCheckBox; +import workspace.ui.elements.UiLabel; +import workspace.ui.elements.UiPanel; +import workspace.ui.event.IActionListener; public class WorkspaceSideBarUi extends UiComponent implements ModelListener { @@ -82,7 +82,7 @@ protected UiLabel getUiLabel() { if (label != null) return label; - label = new UiLabel(); + label = new UiLabel(""); label.setX(xOffset); label.setY(yOffset); label.setTitle("Controls:"); diff --git a/src/main/java/workspace/laf/LookAndFeel.java b/src/main/java/workspace/laf/LookAndFeel.java index cc131e6f..6688e8e2 100644 --- a/src/main/java/workspace/laf/LookAndFeel.java +++ b/src/main/java/workspace/laf/LookAndFeel.java @@ -4,48 +4,35 @@ public class LookAndFeel { - public static void setup() { - setupGizmo(); - setupAxis(); - setupMenu(); - UiValues.put( - UiConstants.KEY_EDITOR_BACKGROUND_COLOR, new Color(60, 60, 60) - ); - UiValues.put(UiConstants.KEY_GRID_COLOR, new Color(74, 74, 74)); - UiValues.put( - UiConstants.KEY_EDITOR_WIREFRAME_COLOR, new Color(241, 152, 45) - ); - } + public static void setup() { + setupGizmo(); + setupAxis(); + setupMenu(); + UiValues.put(UiConstants.KEY_EDITOR_BACKGROUND_COLOR, + new Color(60, 60, 60)); + UiValues.put(UiConstants.KEY_GRID_COLOR, new Color(74, 74, 74)); + UiValues.put(UiConstants.KEY_EDITOR_WIREFRAME_COLOR, + new Color(241, 152, 45)); + } - private static void setupAxis() { - UiValues.put(UiConstants.KEY_AXIS_X_COLOR, new Color(157, 67, 80)); - UiValues.put(UiConstants.KEY_AXIS_Y_COLOR, new Color(109, 148, 46)); - UiValues.put(UiConstants.KEY_AXIS_Z_COLOR, new Color(63, 112, 162)); - } + private static void setupAxis() { + UiValues.put(UiConstants.KEY_AXIS_X_COLOR, new Color(157, 67, 80)); + UiValues.put(UiConstants.KEY_AXIS_Y_COLOR, new Color(109, 148, 46)); + UiValues.put(UiConstants.KEY_AXIS_Z_COLOR, new Color(63, 112, 162)); + } - private static void setupGizmo() { - UiValues.put( - UiConstants.KEY_GIZMO_AXIS_X_COLOR, new Color(221, 56, 79) - ); - UiValues.put( - UiConstants.KEY_GIZMO_AXIS_Y_COLOR, new Color(120, 181, 22) - ); - UiValues.put( - UiConstants.KEY_GIZMO_AXIS_Z_COLOR, new Color(44, 142, 252) - ); - UiValues.put( - UiConstants.KEY_GIZMO_CENTER_COLOR, new Color(200, 200, 200) - ); - } + private static void setupGizmo() { + UiValues.put(UiConstants.KEY_GIZMO_AXIS_X_COLOR, new Color(221, 56, 79)); + UiValues.put(UiConstants.KEY_GIZMO_AXIS_Y_COLOR, new Color(120, 181, 22)); + UiValues.put(UiConstants.KEY_GIZMO_AXIS_Z_COLOR, new Color(44, 142, 252)); + UiValues.put(UiConstants.KEY_GIZMO_CENTER_COLOR, new Color(200, 200, 200)); + } - private static void setupMenu() { - UiValues.put( - UiConstants.KEY_MENU_FOREGROUND_COLOR, new Color(151, 151, 151) - ); - UiValues.put( - UiConstants.KEY_MENU_BACKGROUND_COLOR, new Color(35, 35, 35) - ); - UiValues.put(UiConstants.KEY_MENU_TEXT_SIZE, 12); - } + private static void setupMenu() { + UiValues.put(UiConstants.KEY_MENU_FOREGROUND_COLOR, + new Color(151, 151, 151)); + UiValues.put(UiConstants.KEY_MENU_BACKGROUND_COLOR, new Color(35, 35, 35)); + UiValues.put(UiConstants.KEY_MENU_TEXT_SIZE, 12); + } } diff --git a/src/main/java/workspace/laf/UiConstants.java b/src/main/java/workspace/laf/UiConstants.java index 85449492..df9c3059 100644 --- a/src/main/java/workspace/laf/UiConstants.java +++ b/src/main/java/workspace/laf/UiConstants.java @@ -2,30 +2,30 @@ public class UiConstants { - public static final String KEY_AXIS_X_COLOR = "Axis.x.color"; + public static final String KEY_AXIS_X_COLOR = "Axis.x.color"; - public static final String KEY_AXIS_Y_COLOR = "Axis.y.color"; + public static final String KEY_AXIS_Y_COLOR = "Axis.y.color"; - public static final String KEY_AXIS_Z_COLOR = "Axis.z.color"; + public static final String KEY_AXIS_Z_COLOR = "Axis.z.color"; - public static final String KEY_GRID_COLOR = "Grid.color"; + public static final String KEY_GRID_COLOR = "Grid.color"; - public static final String KEY_EDITOR_BACKGROUND_COLOR = "Editor.background.color"; + public static final String KEY_EDITOR_BACKGROUND_COLOR = "Editor.background.color"; - public static final String KEY_EDITOR_WIREFRAME_COLOR = "Editor.wireframe.color"; + public static final String KEY_EDITOR_WIREFRAME_COLOR = "Editor.wireframe.color"; - public static final String KEY_GIZMO_AXIS_X_COLOR = "Gizmo.x.color"; + public static final String KEY_GIZMO_AXIS_X_COLOR = "Gizmo.x.color"; - public static final String KEY_GIZMO_AXIS_Y_COLOR = "Gizmo.y.color"; + public static final String KEY_GIZMO_AXIS_Y_COLOR = "Gizmo.y.color"; - public static final String KEY_GIZMO_AXIS_Z_COLOR = "Gizmo.z.color"; + public static final String KEY_GIZMO_AXIS_Z_COLOR = "Gizmo.z.color"; - public static final String KEY_GIZMO_CENTER_COLOR = "Gizmo.center.color"; + public static final String KEY_GIZMO_CENTER_COLOR = "Gizmo.center.color"; - public static final String KEY_MENU_FOREGROUND_COLOR = "Menu.foreground.color"; + public static final String KEY_MENU_FOREGROUND_COLOR = "Menu.foreground.color"; - public static final String KEY_MENU_BACKGROUND_COLOR = "Menu.background.color"; + public static final String KEY_MENU_BACKGROUND_COLOR = "Menu.background.color"; - public static final String KEY_MENU_TEXT_SIZE = "Menu.text.size"; + public static final String KEY_MENU_TEXT_SIZE = "Menu.text.size"; } diff --git a/src/main/java/workspace/laf/UiValues.java b/src/main/java/workspace/laf/UiValues.java index 44b9a5c7..52c92a72 100644 --- a/src/main/java/workspace/laf/UiValues.java +++ b/src/main/java/workspace/laf/UiValues.java @@ -7,38 +7,38 @@ public class UiValues { - public static final Color UI_ELEMENT_FOREGROUND = new Color(250, 250, 250); + public static final Color UI_ELEMENT_FOREGROUND = new Color(250, 250, 250); - public static final Color BASE_BLUE = new Color(82, 120, 180); + public static final Color BASE_BLUE = new Color(82, 120, 180); - public static final Color BASE_ORANGE = new Color(199, 135, 83); + public static final Color BASE_ORANGE = new Color(199, 135, 83); - public static final Color SLIDER_BACKGROUND_COLOR = new Color(35, 35, 35); + public static final Color SLIDER_BACKGROUND_COLOR = new Color(35, 35, 35); - public static final Color SLIDER_LIGHT = new Color(89, 89, 89); + public static final Color SLIDER_LIGHT = new Color(89, 89, 89); // public static final float TEXT_SIZE = 12; - private static HashMap mappings; + private static HashMap mappings; - static { - mappings = new HashMap(); - } + static { + mappings = new HashMap(); + } - public static void put(String key, Object value) { - mappings.put(key, value); - } + public static void put(String key, Object value) { + mappings.put(key, value); + } - public static Color getColor(String key) { - return (Color) mappings.get(key); - } + public static Color getColor(String key) { + return (Color) mappings.get(key); + } - public static Font getFont(String key) { - return (Font) mappings.get(key); - } + public static Font getFont(String key) { + return (Font) mappings.get(key); + } - public static int getInt(String key) { - return (int) mappings.get(key); - } + public static int getInt(String key) { + return (int) mappings.get(key); + } } diff --git a/src/main/java/workspace/ui/Color.java b/src/main/java/workspace/ui/Color.java index 1b3cceb6..27df6f3a 100644 --- a/src/main/java/workspace/ui/Color.java +++ b/src/main/java/workspace/ui/Color.java @@ -2,89 +2,89 @@ public class Color { - public static final Color BLACK = new Color(0, 0, 0, 255); + public static final Color BLACK = new Color(0, 0, 0, 255); - public static final Color WHITE = new Color(255, 255, 255, 255); + public static final Color WHITE = new Color(255, 255, 255, 255); - public static final Color RED = new Color(255, 0, 0, 255); + public static final Color RED = new Color(255, 0, 0, 255); - public static final Color GREEN = new Color(0, 255, 0, 255); + public static final Color GREEN = new Color(0, 255, 0, 255); - public static final Color BLUE = new Color(0, 0, 255, 255); + public static final Color BLUE = new Color(0, 0, 255, 255); - public static final Color YELLOW = new Color(255, 255, 0, 255); + public static final Color YELLOW = new Color(255, 255, 0, 255); - public static final Color MAGENTA = new Color(255, 0, 255, 255); + public static final Color MAGENTA = new Color(255, 0, 255, 255); - public static final Color ORANGE = new Color(255, 200, 0, 255); + public static final Color ORANGE = new Color(255, 200, 0, 255); - public static final Color CYAN = new Color(0, 255, 255, 255); + public static final Color CYAN = new Color(0, 255, 255, 255); - public static final Color GRAY = new Color(128, 128, 128, 255); + public static final Color GRAY = new Color(128, 128, 128, 255); - public static final Color DARK_GRAY = new Color(64, 64, 64, 255); + public static final Color DARK_GRAY = new Color(64, 64, 64, 255); - public static final Color LIGHT_GRAY = new Color(192, 192, 192, 255); + public static final Color LIGHT_GRAY = new Color(192, 192, 192, 255); - public static final Color PINK = new Color(255, 175, 175, 255); + public static final Color PINK = new Color(255, 175, 175, 255); - private int red; + private int red; - private int green; + private int green; - private int blue; + private int blue; - private int alpha; + private int alpha; - public Color(int red, int green, int blue) { - this.red = red; - this.green = green; - this.blue = blue; - this.alpha = 255; - } + public Color(int red, int green, int blue) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = 255; + } - public Color(int red, int green, int blue, int alpha) { - this.red = red; - this.green = green; - this.blue = blue; - this.alpha = alpha; - } + public Color(int red, int green, int blue, int alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } - public int getRGBA() { - return ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) - | ((green & 0xFF) << 8) | ((blue & 0xFF) << 0); - } + public int getRGBA() { + return ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) + | ((blue & 0xFF) << 0); + } - public int getRed() { - return red; - } + public int getRed() { + return red; + } - public void setRed(int red) { - this.red = red; - } + public void setRed(int red) { + this.red = red; + } - public int getGreen() { - return green; - } + public int getGreen() { + return green; + } - public void setGreen(int green) { - this.green = green; - } + public void setGreen(int green) { + this.green = green; + } - public int getBlue() { - return blue; - } + public int getBlue() { + return blue; + } - public void setBlue(int blue) { - this.blue = blue; - } + public void setBlue(int blue) { + this.blue = blue; + } - public int getAlpha() { - return alpha; - } + public int getAlpha() { + return alpha; + } - public void setAlpha(int alpha) { - this.alpha = alpha; - } + public void setAlpha(int alpha) { + this.alpha = alpha; + } } diff --git a/src/main/java/workspace/ui/Graphics.java b/src/main/java/workspace/ui/Graphics.java index a18ab463..1d7d95dc 100644 --- a/src/main/java/workspace/ui/Graphics.java +++ b/src/main/java/workspace/ui/Graphics.java @@ -4,48 +4,58 @@ public interface Graphics { - int getWidth(); + int getWidth(); - int getHeight(); + int getHeight(); - void pushMatrix(); + void pushMatrix(); - void popMatrix(); + void popMatrix(); - void translate(float x, float y); + void translate(float x, float y); - void strokeWeight(float weight); + void strokeWeight(float weight); - void setColor(Color color); + void setColor(Color color); - void setColor(int red, int green, int blue); + void setColor(math.Color color); - void drawRect(float x, float y, float width, float height); + void setColor(int red, int green, int blue); - void fillRect(float x, float y, float width, float height); + void drawRect(float x, float y, float width, float height); - void fillFaces(Mesh3D mesh); + void fillRect(float x, float y, float width, float height); - void textSize(float size); + void drawOval(float x, float y, float width, float height); - float getTextSize(); + void fillOval(float x, float y, float width, float height); - float textWidth(String text); + void drawLine(float x1, float y1, float x2, float y2); - float textAscent(); + void fillFaces(Mesh3D mesh); - float textDescent(); + void textSize(float size); - void text(String text, float x, float y); + float getTextSize(); - void enableDepthTest(); + float textWidth(String text); - void disableDepthTest(); + float textAscent(); - void rotateX(float angle); + float textDescent(); - void rotateY(float angle); + void text(String text, float x, float y); - void rotateZ(float angle); + void enableDepthTest(); + + void disableDepthTest(); + + void rotate(float angle); + + void rotateX(float angle); + + void rotateY(float angle); + + void rotateZ(float angle); } diff --git a/src/main/java/workspace/ui/Slider.java b/src/main/java/workspace/ui/Slider.java index 3a14574c..5a6f17f1 100644 --- a/src/main/java/workspace/ui/Slider.java +++ b/src/main/java/workspace/ui/Slider.java @@ -3,88 +3,115 @@ import math.Mathf; import workspace.laf.UiValues; import workspace.ui.border.Insets; +import workspace.ui.event.ISliderCallBack; public class Slider extends UiComponent { - private float value; + private float value; - private float minValue = 0; + private float minValue = 0; - private float maxValue = 3; + private float maxValue = 3; - private float posX; + private float posX; - private ISliderCallBack sliderCallBack; + private ISliderCallBack sliderCallBack; - private String text = "Slider"; + private String text = "Slider"; - @Override - public void onDraw(Graphics g) { - Insets insets = getInsets(); + @Override + public void renderSelf(Graphics g) { + Insets insets = getInsets(); - g.setColor(getBackground()); - g.fillRect( - insets.left, insets.right, width - insets.getWidth(), - height - insets.getHeight() - ); + g.setColor(getBackground()); + g.fillRect(insets.getLeft(), insets.getRight(), + width - insets.getHorizontalInsets(), + height - insets.getVerticalInsets()); - g.setColor(getForeground()); - g.fillRect(getWidth() + posX, 0, 5, getHeight()); + g.setColor(getForeground()); + g.fillRect(getWidth() + posX, 0, 5, getHeight()); - g.setColor(UiValues.SLIDER_LIGHT); - g.fillRect(0, 0, getWidth() + posX, getHeight()); + g.setColor(UiValues.SLIDER_LIGHT); + g.fillRect(0, 0, getWidth() + posX, getHeight()); - g.setColor(foreground); - g.text(" " + value, 4, g.getTextSize() + g.textDescent()); - g.text(text, getWidth() + 10, g.getTextSize() + g.textDescent()); - } + g.setColor(foreground); + g.text(" " + value, 4, g.getTextSize() + g.textDescent()); + g.text(text, getWidth() + 10, g.getTextSize() + g.textDescent()); + } - public void onMouseDragged(int x, int y) { - posX = x - getWidth() - getX(); - updateValue(); - if (sliderCallBack != null) - sliderCallBack.valueChanged(value); - } + public void onMouseDragged(int x, int y) { + posX = x - getWidth() - getX(); + clampPosX(); // Ensure posX stays within bounds + updateValue(); + if (sliderCallBack != null) + sliderCallBack.valueChanged(value); + } - @Override - public void onMouseClicked(int x, int y) { + @Override + public void onMouseClicked(int x, int y) { + // Optionally, jump posX to the clicked position + } - } + private void updateValue() { + // Map posX to the slider's value range +// value = Mathf.map(posX, 0, getWidth(), minValue, maxValue); +// value = Mathf.clamp(value, minValue, maxValue); // Ensure value stays in range + value = maxValue + Mathf.map(posX, 0, getWidth(), minValue, maxValue); + } - private void updateValue() { - value = maxValue + Mathf.map(posX, 0, getWidth(), minValue, maxValue); - } + private void updatePosX() { +// posX = Mathf.map(value, minValue, maxValue, x, x + width); - public String getText() { - return text; - } + // Map value back to posX +// posX = Mathf.map(value, minValue, maxValue, x, x + getWidth()); +// clampPosX(); // Ensure posX stays within bounds + } - public void setText(String text) { - this.text = text; - } + private void clampPosX() { +// // Ensure posX doesn't exceed slider's drawable area +// posX = Mathf.clamp(posX, 0, getWidth()); + } - public ISliderCallBack getSliderCallBack() { - return sliderCallBack; - } + public float getValue() { + return value; + } - public void setSliderCallBack(ISliderCallBack sliderCallBack) { - this.sliderCallBack = sliderCallBack; - } + public void setValue(float value) { +// this.value = Mathf.clamp(value, minValue, maxValue); // Clamp the value + updatePosX(); // Update posX to reflect the new value + } - public float getMinValue() { - return minValue; - } + public String getText() { + return text; + } - public void setMinValue(float minValue) { - this.minValue = minValue; - } + public void setText(String text) { + this.text = text; + } - public float getMaxValue() { - return maxValue; - } + public ISliderCallBack getSliderCallBack() { + return sliderCallBack; + } - public void setMaxValue(float maxValue) { - this.maxValue = maxValue; - } + public void setSliderCallBack(ISliderCallBack sliderCallBack) { + this.sliderCallBack = sliderCallBack; + } -} + public float getMinValue() { + return minValue; + } + + public void setMinValue(float minValue) { + this.minValue = minValue; + updatePosX(); // Update posX to reflect any change in range + } + + public float getMaxValue() { + return maxValue; + } + + public void setMaxValue(float maxValue) { + this.maxValue = maxValue; + updatePosX(); // Update posX to reflect any change in range + } +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/UiComponent.java b/src/main/java/workspace/ui/UiComponent.java index a835d935..39c72eca 100644 --- a/src/main/java/workspace/ui/UiComponent.java +++ b/src/main/java/workspace/ui/UiComponent.java @@ -3,207 +3,255 @@ import java.util.ArrayList; import java.util.List; -import workspace.ui.border.IBorder; +import workspace.ui.border.Border; import workspace.ui.border.Insets; +import workspace.ui.elements.UiElement; import workspace.ui.layout.Layout; +import workspace.ui.renderer.Renderer; + +public class UiComponent implements UiElement { + + protected int x; + + protected int y; + + protected int width; + + protected int height; + + protected boolean visible; + + protected Color foreground; + + protected Color background; + + protected Border border; + + protected List components; + + protected Layout layout; + + private Renderer renderer; + + public UiComponent() { + this(0, 0, 0, 0, true, Color.BLACK, Color.GRAY); + } + + public UiComponent(int x, int y, int width, int height, boolean visible, + Color foreground, Color background) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.visible = visible; + this.foreground = foreground; + this.background = background; + this.components = new ArrayList(); + } + + @Override + public void setRenderer(Renderer renderer) { + this.renderer = renderer; + } + + @Override + public Renderer getRenderer() { + return renderer; + } + + @Override + public Insets getInsets() { + if (border == null) + return new Insets(); + return border.getInsets(); + } + + + @Override + public void setBorder(Border border) { + this.border = border; + } + + @Override + public void render(Graphics g) { + if (!visible) + return; + g.pushMatrix(); + g.translate(x, y); + renderSelf(g); + renderBorder(g); + g.translate(getInsets().getLeft(), getInsets().getTop()); + renderChildren(g); + g.popMatrix(); + } + + /** + * Renders the border of the UI element if a border is defined. + *

+ * This method checks if the {@code border} is not null before calling the + * {@code renderBorder} method on the provided graphics context. It uses the + * element's current width and height to define the area of the border. + *

+ * + * @param g the graphics context to draw on. + */ + protected void renderBorder(Graphics g) { + if (border == null) + return; + border.renderBorder(g, 0, 0, getWidth(), getHeight()); + } + + protected void renderChildren(Graphics g) { + for (UiComponent component : components) { + component.render(g); + } + } + + @Override + public void setLayout(Layout layout) { + this.layout = layout; + } + + @Override + public boolean contains(int x, int y) { + return x >= this.x && y >= this.y && x <= (this.x + getWidth()) + && y <= (this.y + getHeight()); + } + + @Override + public void setVisible(boolean visible) { + this.visible = visible; + } + + @Override + public boolean isVisible() { + return visible; + } + + public void renderSelf(Graphics g) { + if (renderer == null) + return; + renderer.render(g, this); + } + + protected void layout() { + if (layout == null) + return; + layout.layout(this); + } + + public void onMouseClicked(int x, int y) { + if (!isVisible()) + return; + for (UiComponent component : components) { + if (component.contains(x - this.x, y - this.y)) { + component.onMouseClicked(x, y); + } + } + } + + public void onMouseDragged(int x, int y) { + if (!isVisible()) + return; + for (UiComponent component : components) { + if (component.contains(x - this.x, y - this.y)) { + component.onMouseDragged(x, y); + } + } + } + + public void onMouseReleased(int x, int y) { + if (!isVisible()) + return; + for (UiComponent component : components) { + if (component.contains(x - this.x, y - this.y)) { + component.onMouseReleased(x, y); + } + } + } + + public void onMousePressed(int x, int y) { + if (!isVisible()) + return; + for (UiComponent component : components) { + if (component.contains(x - this.x, y - this.y)) { + component.onMousePressed(x, y); + } + } + } + + // USED + public void add(UiComponent component) { + if (component == null) + return; + components.add(component); + layout(); + } +// +// public void remove(UiComponent component) { +// if (component == null) +// return; +// components.remove(component); +// layout(); +// } +// +// public int getComponentCount() { +// return components.size(); +// } +// +// public UiComponent getComponentAt(int index) { +// return components.get(index); +// } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + @Override + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + @Override + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public Color getForeground() { + return foreground; + } + + public void setForeground(Color foreground) { + this.foreground = foreground; + } + + public Color getBackground() { + return background; + } + + public void setBackground(Color background) { + this.background = background; + } -public class UiComponent { - - protected int x; - - protected int y; - - protected int width; - - protected int height; - - protected boolean visible; - - protected Color foreground; - - protected Color background; - - protected IBorder border; - - protected List components; - - protected Layout layout; - - public UiComponent() { - this(0, 0, 0, 0, true, Color.BLACK, Color.GRAY); - } - - public UiComponent(int x, int y, int width, int height, boolean visible, - Color foreground, Color background) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.visible = visible; - this.foreground = foreground; - this.background = background; - this.components = new ArrayList(); - } - - public void draw(Graphics g) { - if (!visible) - return; - g.pushMatrix(); - g.translate(x, y); - onDraw(g); - drawBorder(g); - g.translate(getInsets().left, getInsets().top); - drawChildren(g); - g.popMatrix(); - } - - public void onDraw(Graphics g) { - - } - - protected void layout() { - if (layout == null) - return; - layout.layout(this); - } - - protected void drawBorder(Graphics g) { - IBorder border = getBorder(); - if (border == null) - return; - border.drawBorder(g, 0, 0, getWidth(), getHeight()); - } - - protected void drawChildren(Graphics g) { - for (UiComponent component : components) { - component.draw(g); - } - } - - public boolean contains(int x, int y) { - return x >= this.x && y >= this.y && x <= (this.x + getWidth()) - && y <= (this.y + getHeight()); - } - - public void onMouseClicked(int x, int y) { - if (!isVisible()) - return; - for (UiComponent component : components) { - if (component.contains(x - this.x, y - this.y)) { - component.onMouseClicked(x, y); - } - } - } - - public void onMouseDragged(int x, int y) { - if (!isVisible()) - return; - for (UiComponent component : components) { - if (component.contains(x - this.x, y - this.y)) { - component.onMouseDragged(x, y); - } - } - } - - public void add(UiComponent component) { - if (component == null) - return; - components.add(component); - layout(); - } - - public void remove(UiComponent component) { - if (component == null) - return; - components.remove(component); - layout(); - } - - public int getComponentCount() { - return components.size(); - } - - public UiComponent getComponentAt(int index) { - return components.get(index); - } - - public Layout getLayout() { - return layout; - } - - public void setLayout(Layout layout) { - this.layout = layout; - } - - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; - } - - public int getY() { - return y; - } - - public void setY(int y) { - this.y = y; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - - public boolean isVisible() { - return visible; - } - - public void setVisible(boolean visible) { - this.visible = visible; - } - - public Color getForeground() { - return foreground; - } - - public void setForeground(Color foreground) { - this.foreground = foreground; - } - - public Color getBackground() { - return background; - } - - public void setBackground(Color background) { - this.background = background; - } - - public Insets getInsets() { - IBorder border = getBorder(); - if (border == null) - return new Insets(); - return border.getInsets(); - } - - public IBorder getBorder() { - return border; - } - - public void setBorder(IBorder border) { - this.border = border; - } } diff --git a/src/main/java/workspace/ui/UiSimpleList.java b/src/main/java/workspace/ui/UiSimpleList.java index a9d8c461..869fc01f 100644 --- a/src/main/java/workspace/ui/UiSimpleList.java +++ b/src/main/java/workspace/ui/UiSimpleList.java @@ -5,46 +5,44 @@ public class UiSimpleList extends UiComponent { - private List elements; - - public UiSimpleList() { - elements = new ArrayList(); - setBackground(background); - } - - public List getElements() { - return elements; - } - - public void addElements(Object... elements) { - for (Object e : elements) { - this.elements.add(e); - } - } - - @Override - public void onDraw(Graphics g) { - int y = 300; - int padding = 5; - int gap = 5; - - g.pushMatrix(); - g.setColor(getBackground()); - g.fillRect(0, 0, height, width); - - for (Object e : elements) { - String text = e.toString(); - g.setColor(background); - g.fillRect( - 0, y, getWidth(), - g.textAscent() + g.textDescent() + padding + padding - ); - g.setColor(foreground); - g.text(text, padding, y + g.getTextSize() + padding); - y += g.getTextSize() + gap + padding + padding; - } - - g.popMatrix(); - } + private List elements; + + public UiSimpleList() { + elements = new ArrayList(); + setBackground(background); + } + + public List getElements() { + return elements; + } + + public void addElements(Object... elements) { + for (Object e : elements) { + this.elements.add(e); + } + } + + @Override + public void renderSelf(Graphics g) { + int y = 300; + int padding = 5; + int gap = 5; + + g.pushMatrix(); + g.setColor(getBackground()); + g.fillRect(0, 0, height, width); + + for (Object e : elements) { + String text = e.toString(); + g.setColor(background); + g.fillRect(0, y, getWidth(), + g.textAscent() + g.textDescent() + padding + padding); + g.setColor(foreground); + g.text(text, padding, y + g.getTextSize() + padding); + y += g.getTextSize() + gap + padding + padding; + } + + g.popMatrix(); + } } diff --git a/src/main/java/workspace/ui/border/Border.java b/src/main/java/workspace/ui/border/Border.java index a53a8171..7d9309a7 100644 --- a/src/main/java/workspace/ui/border/Border.java +++ b/src/main/java/workspace/ui/border/Border.java @@ -2,10 +2,34 @@ import workspace.ui.Graphics; -public interface IBorder { +/** + * Represents a customizable border for UI elements. + * + * A `Border` defines how to visually render a boundary around a UI element and + * specifies the spacing (insets) it requires. + */ +public interface Border { - void drawBorder(Graphics g2d, int x, int y, int width, int height); + /** + * Renders the border using the provided Graphics context. + * + * @param g The Graphics context for rendering. + * @param x The x-coordinate of the border's position. + * @param y The y-coordinate of the border's position. + * @param width The width of the area to render the border around. + * @param height The height of the area to render the border around. + */ + void renderBorder(Graphics g, int x, int y, int width, int height); - Insets getInsets(); + /** + * Returns the insets defined by the border. + * + * Insets specify the spacing required between the border and the content of + * the UI element. These insets are typically used in layout calculations. + * + * @return An {@link Insets} object representing the top, left, bottom, and + * right insets of the border. + */ + Insets getInsets(); -} +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/border/CompoundBorder.java b/src/main/java/workspace/ui/border/CompoundBorder.java new file mode 100644 index 00000000..246e6953 --- /dev/null +++ b/src/main/java/workspace/ui/border/CompoundBorder.java @@ -0,0 +1,96 @@ +package workspace.ui.border; + +import workspace.ui.Graphics; + +/** + * A border implementation that combines two borders: an outer border and an + * inner border. The outer border is rendered first, followed by the inner + * border, which is adjusted to fit within the insets of the outer border. + */ +public class CompoundBorder implements Border { + + private final Border outerBorder; + + private final Border innerBorder; + + /** + * Creates a CompoundBorder with the specified outer and inner borders. + * + * @param outerBorder The outer border. Cannot be null. + * @param innerBorder The inner border. Cannot be null. + * @throws NullPointerException if either outerBorder or innerBorder is null. + */ + public CompoundBorder(Border outerBorder, Border innerBorder) { + if (outerBorder == null) { + throw new NullPointerException("Outer border cannot be null."); + } + if (innerBorder == null) { + throw new NullPointerException("Inner border cannot be null."); + } + this.outerBorder = outerBorder; + this.innerBorder = innerBorder; + } + + /** + * Returns the combined insets of the outer and inner borders. + * + * @return An Insets object representing the total insets of the compound + * border. + */ + @Override + public Insets getInsets() { + Insets insets = new Insets(outerBorder.getInsets()); + insets.add(innerBorder.getInsets()); + return insets; + } + + /** + * Renders the outer border and adjusts the coordinates and dimensions to + * render the inner border within the remaining area. + * + * @param g The Graphics context for rendering. + * @param x The x-coordinate of the top-left corner. + * @param y The y-coordinate of the top-left corner. + * @param width The width of the area to render the border around. + * @param height The height of the area to render the border around. + */ + @Override + public void renderBorder(Graphics g, int x, int y, int width, int height) { + renderOuterBorder(g, x, y, width, height); + renderInnerBorder(g, x, y, width, height); + } + + /** + * Renders the outer border using the provided Graphics context. + * + * @param g The Graphics context for rendering. + * @param x The x-coordinate of the top-left corner. + * @param y The y-coordinate of the top-left corner. + * @param width The width of the area to render the border around. + * @param height The height of the area to render the border around. + */ + private void renderOuterBorder(Graphics g, int x, int y, int width, + int height) { + outerBorder.renderBorder(g, x, y, width, height); + } + + /** + * Renders the inner border by adjusting for the insets of the outer border. + * + * @param g The Graphics context for rendering. + * @param x The x-coordinate of the top-left corner. + * @param y The y-coordinate of the top-left corner. + * @param width The width of the area to render the border around. + * @param height The height of the area to render the border around. + */ + private void renderInnerBorder(Graphics g, int x, int y, int width, + int height) { + Insets outerInsets = outerBorder.getInsets(); + x += outerInsets.getLeft(); + y += outerInsets.getTop(); + width -= outerInsets.getHorizontalInsets(); + height -= outerInsets.getVerticalInsets(); + innerBorder.renderBorder(g, x, y, width, height); + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/border/CornerGapBorder.java b/src/main/java/workspace/ui/border/CornerGapBorder.java new file mode 100644 index 00000000..94202cf3 --- /dev/null +++ b/src/main/java/workspace/ui/border/CornerGapBorder.java @@ -0,0 +1,172 @@ +package workspace.ui.border; + +import math.Color; +import workspace.ui.Graphics; + +/** + * A custom border implementation that mimics pixel art-style UI borders with + * missing pixels at each corner. The "size" parameter defines the scale of + * these gaps, creating a distinctive visual effect similar to pixel art + * designs. + *

+ * This border has gaps (or empty space) at each corner, allowing it to fit + * stylistically into a pixel art-themed user interface. + *

+ */ +public class CornerGapBorder implements Border { + + private int x; + + private int y; + + private int width; + + private int height; + + private int size; + + private Color color; + + private Graphics g; + + /** + * Creates a new instance of a CornerGapBorder. + * + * @param size Defines the scale of the missing gaps at each corner (in + * pixels). Larger values increase the size of these gaps, + * influencing how much of the border is visually omitted at the + * corners. + * @param color The color used for the visible parts of the border. + * @throws NullPointerException if the provided color is null. + */ + public CornerGapBorder(int size, Color color) { + this.size = size; + this.color = color; + } + + /** + * Renders the border around a specified rectangle area with gaps at each + * corner, giving it the appearance of a pixel art-style border. + * + * @param g The graphics context used to draw the border. + * @param x The x-coordinate of the top-left corner of the area to draw. + * @param y The y-coordinate of the top-left corner of the area to draw. + * @param width The width of the rectangular area to render the border + * around. + * @param height The height of the rectangular area to render the border + * around. + */ + @Override + public void renderBorder(Graphics g, int x, int y, int width, int height) { + setGraphicsContext(g); + setBorderFrame(x, y, width, height); + setColor(); + renderRectangles(); + } + + /** + * Draws the visual rectangles forming the North, West, South, and East sides + * of the border, each leaving a gap at the corners to achieve the pixel art + * aesthetic. + */ + private void renderRectangles() { + renderNorthRectangle(); + renderWestRectangle(); + renderSouthRectangle(); + renderEastRectangle(); + } + + /** Renders the North side of the border with a missing corner gap. */ + private void renderNorthRectangle() { + renderRectangle(x + size, y, getRectangleWidth(), size); + } + + /** Renders the West side of the border with a missing corner gap. */ + private void renderWestRectangle() { + renderRectangle(x, y + size, size, getRectangleHeight()); + } + + /** Renders the South side of the border with a missing corner gap. */ + private void renderSouthRectangle() { + renderRectangle(x + size, y + height - size, getRectangleWidth(), size); + } + + /** Renders the East side of the border with a missing corner gap. */ + private void renderEastRectangle() { + renderRectangle(x + width - size, y + size, size, getRectangleHeight()); + } + + /** + * Renders a rectangle using the provided graphics context. + * + * @param x The x-coordinate for the top-left corner of the rectangle. + * @param y The y-coordinate for the top-left corner of the rectangle. + * @param width The width of the rectangle to draw. + * @param height The height of the rectangle to draw. + */ + private void renderRectangle(int x, int y, int width, int height) { + g.fillRect(x, y, width, height); + } + + /** + * Calculates the visible rectangle's width by excluding the corner gaps. + * + * @return The calculated width of the visible rectangle. + */ + private int getRectangleWidth() { + return width - size - size; + } + + /** + * Calculates the visible rectangle's height by excluding the corner gaps. + * + * @return The calculated height of the visible rectangle. + */ + private int getRectangleHeight() { + return height - size - size; + } + + /** + * Sets the color for rendering using the graphics context. + */ + private void setColor() { + g.setColor(color); + } + + /** + * Configures the rectangle's frame to know the drawing boundaries for + * rendering. + * + * @param x The x-coordinate of the top-left corner. + * @param y The y-coordinate of the top-left corner. + * @param width The width of the area to draw. + * @param height The height of the area to draw. + */ + private void setBorderFrame(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Sets the graphics context to the current instance for use during rendering. + * + * @param g The provided graphics context. + */ + private void setGraphicsContext(Graphics g) { + this.g = g; + } + + /** + * Retrieves the Insets required for this border. Insets define the amount of + * space the border occupies on all four sides. + * + * @return Insets representing the "size" of the gaps in all directions. + */ + @Override + public Insets getInsets() { + return new Insets(size, size, size, size); + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/border/EmptyBorder.java b/src/main/java/workspace/ui/border/EmptyBorder.java new file mode 100644 index 00000000..ebb714df --- /dev/null +++ b/src/main/java/workspace/ui/border/EmptyBorder.java @@ -0,0 +1,53 @@ +package workspace.ui.border; + +import workspace.ui.Graphics; + +/** + * A border implementation that does not render any visual element but provides + * space defined by its insets. + */ +public class EmptyBorder implements Border { + + private final Insets insets; + + /** + * Creates an EmptyBorder with the specified insets. + * + * @param top The inset size at the top. + * @param left The inset size at the left. + * @param bottom The inset size at the bottom. + * @param right The inset size at the right. + * @throws IllegalArgumentException if any of the inset values are negative. + */ + public EmptyBorder(int top, int left, int bottom, int right) { + if (top < 0 || left < 0 || bottom < 0 || right < 0) { + throw new IllegalArgumentException("Insets cannot be negative."); + } + insets = new Insets(top, left, bottom, right); + } + + /** + * Does not render anything, as this border is visually empty. + * + * @param g2 The Graphics context for rendering. + * @param x The x-coordinate of the top-left corner. + * @param y The y-coordinate of the top-left corner. + * @param width The width of the area to render the border around. + * @param height The height of the area to render the border around. + */ + @Override + public void renderBorder(Graphics g, int x, int y, int width, int height) { + // No rendering logic, as this border is empty. + } + + /** + * Returns the insets of the border. + * + * @return A new Insets object representing the border's insets. + */ + @Override + public Insets getInsets() { + return new Insets(insets); + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/border/Insets.java b/src/main/java/workspace/ui/border/Insets.java index 68e93e7d..9ebc567a 100644 --- a/src/main/java/workspace/ui/border/Insets.java +++ b/src/main/java/workspace/ui/border/Insets.java @@ -1,209 +1,219 @@ package workspace.ui.border; /** - * TODO Explain me - * - * @author - Simon Dietz - * @version 0.1, 17 January 2011 + * Represents a 2D Insets model, typically used for defining spacing around UI + * elements (e.g., margins, padding, or borders). * + *

+ * Each side of the inset (top, left, bottom, right) can be configured + * independently or uniformly. The class also provides utility methods for + * computing total horizontal and vertical spacing, and supports operations like + * adding insets or copying values. + *

*/ -public class Insets implements Cloneable { - - /** - * The distance from top. - */ - public int top; - - /** - * The distance from left. - */ - public int left; - - /** - * The distance from bottom. - */ - public int bottom; - - /** - * The distance from right. - */ - public int right; - - /** - * Constructs a new instance of this insets with the top, left, bottom and - * right values set to zero. - */ - public Insets() { - super(); - } - - /** - * Constructs a new instance of this insets with all distances set to the - * specified value. - * - * @param i the value for the distance from top, left, bottom and right. - */ - public Insets(int i) { - this(i, i, i, i); - } - - /** - * Constructs a new instance of this insets with the specified top, left, - * bottom and right values. - * - * @param top the value for the distance from top - * @param left the value for the distance from left - * @param bottom the value for the distance from bottom - * @param right the value for the distance from right - */ - public Insets(int top, int left, int bottom, int right) { - super(); - this.top = top; - this.left = left; - this.bottom = bottom; - this.right = right; - } - - /** - * Constructs a new instance of this insets with all distances same as the - * ones of the given insets object. - * - * @param i the insets to copy the distance values from - */ - public Insets(Insets i) { - this.top = i.top; - this.left = i.left; - this.bottom = i.bottom; - this.right = i.right; - } - - /** - * Adds the distances of the specified insets object to the distances of - * this insets. - * - * @param i the insets being added - */ - public void add(Insets i) { - this.top += i.top; - this.left += i.left; - this.bottom += i.bottom; - this.right += i.right; - } - - /** - * Subtracts the distances of the specified insets object from the distances - * of this insets. - * - * @param i the insets being subtracted - */ - public void sub(Insets i) { - this.top -= i.top; - this.left -= i.left; - this.bottom -= i.bottom; - this.right -= i.right; - } - - /** - * Returns the sum of left and right - * - * @return the sum of left and right - */ - public int getWidth() { - return left + right; - } - - /** - * Returns the sum of top and bottom. - * - * @return the sum of top and bottom - */ - public int getHeight() { - return top + bottom; - } - - /** - * Determines if all distances of this insets are equal to zero. - * - * @return true if all distances of this insets are equal to zero - */ - public boolean isEmpty() { - return top == 0 && left == 0 && bottom == 0 && right == 0; - } - - public int getHorizontalInsets() { - return left + right; - } - - public int getVerticalInsets() { - return top + bottom; - } - - public void set(int size) { - top = left = bottom = right = size; - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + bottom; - result = prime * result + left; - result = prime * result + right; - result = prime * result + top; - return result; - } - - /** - * Checks if the specified object is equal to this insets. Two insets are - * equal if all of there distances (top, left, bottom, right) are equal. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Insets other = (Insets) obj; - if (bottom != other.bottom) - return false; - if (left != other.left) - return false; - if (right != other.right) - return false; - if (top != other.top) - return false; - return true; - } - - /** - * Creates a copy of this insets. - * - * @return a copy of this insets - */ - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - /** - * Returns a string representation of this insets. - * - * @return a string representation of this insets - */ - @Override - public String toString() { - return "Insets [top=" + top + ", left=" + left + ", bottom=" + bottom - + ", right=" + right + "]"; - } - -} +public class Insets { + + /** + * The inset value for the top side. + */ + private int top; + + /** + * The inset value for the left side. + */ + private int left; + + /** + * The inset value for the bottom side. + */ + private int bottom; + + /** + * The inset value for the right side. + */ + private int right; + + /** + * Creates a default Insets object with all sides set to zero. + */ + public Insets() { + this(0, 0, 0, 0); + } + + /** + * Creates a new Insets object as a copy of an existing one. + * + * @param insets the Insets object to copy + * @throws NullPointerException if the provided insets object is null + */ + public Insets(Insets insets) { + if (insets == null) { + throw new NullPointerException("Insets to copy cannot be null."); + } + this.top = insets.top; + this.left = insets.left; + this.bottom = insets.bottom; + this.right = insets.right; + } + + /** + * Creates an Insets object with explicit values for each side. + * + * @param top the inset value for the top side + * @param left the inset value for the left side + * @param bottom the inset value for the bottom side + * @param right the inset value for the right side + * @throws IllegalArgumentException if any value is negative + */ + public Insets(int top, int left, int bottom, int right) { + set(top, left, bottom, right); + } + + /** + * Adds the values of another Insets object to this one. + * + * @param other the Insets object whose values will be added + * @throws NullPointerException if the provided insets object is null + */ + public void add(Insets other) { + if (other == null) { + throw new NullPointerException("Insets to add cannot be null."); + } + this.top += other.top; + this.left += other.left; + this.bottom += other.bottom; + this.right += other.right; + } + + /** + * Sets the same value for all four sides. + * + * @param size the uniform value for all sides + * @throws IllegalArgumentException if the value is negative + */ + public void set(int size) { + set(size, size, size, size); + } + + /** + * Sets the values for each side explicitly. + * + * @param top the inset value for the top side + * @param left the inset value for the left side + * @param bottom the inset value for the bottom side + * @param right the inset value for the right side + * @throws IllegalArgumentException if any value is negative + */ + public void set(int top, int left, int bottom, int right) { + if (top < 0 || left < 0 || bottom < 0 || right < 0) { + throw new IllegalArgumentException("Insets values cannot be negative."); + } + this.top = top; + this.left = left; + this.bottom = bottom; + this.right = right; + } + + /** + * Returns the total horizontal spacing (left + right). + * + * @return the sum of the left and right inset values + */ + public int getHorizontalInsets() { + return left + right; + } + + /** + * Returns the total vertical spacing (top + bottom). + * + * @return the sum of the top and bottom inset values + */ + public int getVerticalInsets() { + return top + bottom; + } + + /** + * Returns the inset value for the top side. + * + * @return the top inset value + */ + public int getTop() { + return top; + } + + /** + * Returns the inset value for the left side. + * + * @return the left inset value + */ + public int getLeft() { + return left; + } + + /** + * Returns the inset value for the bottom side. + * + * @return the bottom inset value + */ + public int getBottom() { + return bottom; + } + + /** + * Returns the inset value for the right side. + * + * @return the right inset value + */ + public int getRight() { + return right; + } + + /** + * Checks if this Insets object is equal to another object. + * + * @param obj the object to compare + * @return true if the other object is an Insets instance with identical + * values; false otherwise + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Insets insets = (Insets) obj; + return top == insets.top && left == insets.left && bottom == insets.bottom + && right == insets.right; + } + + /** + * Computes a hash code for this Insets object, consistent with the + * {@link #equals(Object)} method. + * + * @return the hash code for this Insets object + */ + @Override + public int hashCode() { + int result = 17; + result = 31 * result + top; + result = 31 * result + left; + result = 31 * result + bottom; + result = 31 * result + right; + return result; + } + + /** + * Returns a string representation of the Insets object, useful for debugging. + * + * @return a string describing the insets in the format: "Insets [top=..., + * left=..., bottom=..., right=...]" + */ + @Override + public String toString() { + return "Insets [top=" + top + ", left=" + left + ", bottom=" + bottom + + ", right=" + right + "]"; + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/border/LineBorder.java b/src/main/java/workspace/ui/border/LineBorder.java index 7debccd2..93c8274f 100644 --- a/src/main/java/workspace/ui/border/LineBorder.java +++ b/src/main/java/workspace/ui/border/LineBorder.java @@ -1,100 +1,93 @@ package workspace.ui.border; -import workspace.ui.Color; +import math.Color; import workspace.ui.Graphics; -public class LineBorder extends AbstractBorder { +/** + * A border implementation that draws a solid line around a UI element. The + * thickness and color of the border can be customized. + */ +public class LineBorder implements Border { - /** - * The width of this border in pixels. - */ - int width; + private final int size; - /** - * The color of this border. - */ - Color color; + private final Color color; - /** - * Constructs a new instance of this border with the specified color and a - * width of 1 pixel. - * - * @param color the color of this border - */ - public LineBorder(Color color) { - this(color, 1); - } + /** + * Creates a LineBorder with the specified thickness and color. + * + * @param size The thickness of the border. Must be non-negative. + * @param color The color of the border. Cannot be null. + * @throws IllegalArgumentException if size is negative. + * @throws NullPointerException if color is null. + */ + public LineBorder(int size, Color color) { + if (size < 0) { + throw new IllegalArgumentException("Border size must be non-negative."); + } + if (color == null) { + throw new NullPointerException("Color cannot be null."); + } + this.size = size; + this.color = color; + } - /** - * Constructs a new instance of this border with the specified color and - * width. - * - * @param width the width of this border in pixels - * @param color the color of this border - */ - public LineBorder(Color color, int width) { - super(); - this.color = color; - this.width = width; - } + /** + * Renders the border by drawing nested rectangles to achieve the desired + * thickness. + * + * @param g The Graphics context for rendering. + * @param x The x-coordinate of the top-left corner. + * @param y The y-coordinate of the top-left corner. + * @param width The width of the area to render the border around. + * @param height The height of the area to render the border around. + */ + @Override + public void renderBorder(Graphics g, int x, int y, int width, int height) { + g.setColor(color); + g.fillRect(x, y, width, size); // Top + g.fillRect(x, y + size, size, height - size * 2); // Left + g.fillRect(x + width - size, y + size, size, height - size * 2); // Right + g.fillRect(x, y + height - size, width, size); // Bottom + } - /** - * Returns the width of this border in pixels. - * - * @return the width of this border - */ - public int getWidth() { - return width; - } + /** + * Returns the insets required for this border, which are equal to the border + * thickness on all sides. + * + * @return An Insets object with the border thickness. + */ + @Override + public Insets getInsets() { + return new Insets(size, size, size, size); + } - /** - * Sets the width of this border to the specified new value. - * - * @param width the new width for this border - */ - public void setWidth(int width) { - // FIXME Providing a setter may not be that good, cause the component - // using - // this border has to change it's layout (insets) - this.width = width; - } + /** + * Gets the size (thickness) of the border. + * + * @return The border thickness. + */ + public int getSize() { + return size; + } - /** - * Returns the color of this border. - * - * @return the color of this border - */ - public Color getColor() { - return color; - } + /** + * Gets the color of the border. + * + * @return The border color. + */ + public Color getColor() { + return color; + } - /** - * Sets the color of this border to the specified new value. - * - * @param color the new color for this border - */ - public void setColor(Color color) { - this.color = color; - } + /** + * Returns a string representation of the LineBorder for debugging. + * + * @return A string with the border's properties. + */ + @Override + public String toString() { + return "LineBorder[size=" + size + ", color=" + color + "]"; + } - /** - * {@inheritDoc} - */ - @Override - public Insets getInsets() { - return new Insets(width); - } - - /** - * {@inheritDoc} - */ - @Override - public void drawBorder(Graphics g, int x, int y, int width, int height) { - g.strokeWeight(1); - g.setColor(color); - for (int i = 0; i < this.width; i++) { - g.drawRect(x + i, y + i, width - i - i - 1, height - i - i - 1); - } - } - -} +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/UiButton.java b/src/main/java/workspace/ui/elements/UiButton.java similarity index 100% rename from src/main/java/workspace/ui/UiButton.java rename to src/main/java/workspace/ui/elements/UiButton.java diff --git a/src/main/java/workspace/ui/UiCheckBox.java b/src/main/java/workspace/ui/elements/UiCheckBox.java similarity index 100% rename from src/main/java/workspace/ui/UiCheckBox.java rename to src/main/java/workspace/ui/elements/UiCheckBox.java diff --git a/src/main/java/workspace/ui/UiEditorMenu.java b/src/main/java/workspace/ui/elements/UiEditorMenu.java similarity index 100% rename from src/main/java/workspace/ui/UiEditorMenu.java rename to src/main/java/workspace/ui/elements/UiEditorMenu.java diff --git a/src/main/java/workspace/ui/elements/UiElement.java b/src/main/java/workspace/ui/elements/UiElement.java new file mode 100644 index 00000000..4f44f24e --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiElement.java @@ -0,0 +1,170 @@ +package workspace.ui.elements; + +import workspace.ui.Color; +import workspace.ui.Graphics; +import workspace.ui.border.Border; +import workspace.ui.border.Insets; +import workspace.ui.layout.Layout; +import workspace.ui.renderer.Renderer; + +/** + * Core abstraction for all UI elements. + *

+ * This interface defines the essential methods required for a UI element, such + * as rendering, layout management, and interaction capabilities. + * Implementations of this interface represent individual components within a + * user interface hierarchy. + *

+ */ +public interface UiElement { + + /** + * Renders this {@code UiElement} using its specific rendering logic. + *

+ * The rendering logic should account for the element's visual representation, + * including its position, size, background, border, and children if + * applicable. + *

+ * + * @param g The {@link Graphics} context used for drawing. + */ + void render(Graphics g); + + /** + * Sets a custom renderer for this {@code UiElement}. + * + * @param renderer The {@link Renderer} to assign to this element. A + * {@code null} value may disable custom rendering logic or + * fall back to defaults. + */ + void setRenderer(Renderer renderer); + + /** + * Retrieves the current renderer assigned to this UI element. + * + * @return The {@link Renderer} currently used by this UI element. + */ + Renderer getRenderer(); + + /** + * Assigns a layout manager to this {@code UiElement}. + *

+ * The layout manager defines how this element's child components are + * positioned and sized. Assigning a new layout manager should immediately + * trigger a re-layout of the element's children. + *

+ * + * @param layout The {@link Layout} manager to set. A {@code null} value may + * default to a no-op layout or an implementation-specific + * behavior. + */ + void setLayout(Layout layout); + + /** + * Determines if the specified coordinates are within the bounds of this + * {@code UiElement}. + *

+ * The coordinates are relative to the element's parent or local coordinate + * system. This method can be used for hit-testing, such as determining + * whether a mouse event occurred within the element's area. + *

+ * + * @param x The X-coordinate to check. + * @param y The Y-coordinate to check. + * @return {@code true} if the coordinates are within this element's bounds; + * {@code false} otherwise. + */ + boolean contains(int x, int y); + + /** + * Checks if this {@code UiElement} is currently visible. + *

+ * A visible {@code UiElement} is rendered and participates in interactions + * such as event handling. Invisible elements are typically excluded from + * rendering and interaction logic. + *

+ * + * @return {@code true} if the element is visible; {@code false} otherwise. + */ + boolean isVisible(); + + /** + * Sets the visibility of this {@code UiElement}. + *

+ * Setting visibility to {@code false} hides the element from rendering and + * may exclude it from event handling or layout calculations. Setting it to + * {@code true} restores its visibility and functionality. + *

+ * + * @param visible {@code true} to make the element visible, or {@code false} + * to hide it. + */ + void setVisible(boolean visible); + + /** + * Sets the border for this {@code UiElement}. + *

+ * The border defines the visual boundary or outline surrounding the UI + * element. It can be styled or configured to visually separate UI components + * or provide additional visual context. Setting a new border will affect the + * element's appearance during rendering. + *

+ * + * @param border The {@link Border} to associate with this UI element. A + * {@code null} value may remove any existing border from the + * element. + */ + void setBorder(Border border); + + /** + * Retrieves the insets (margins or padding) for this {@code UiElement}. + *

+ * Insets define the space between the element's content and its border or + * edges. This can represent padding within the UI element or margins for + * layout purposes. If no border or layout manager defines specific insets, + * the method will return a default empty insets object. + *

+ * + * @return An instance of {@link Insets} representing the current insets for + * this UI element. + */ + Insets getInsets(); + + /** + * Retrieves the width of this {@code UiElement}. + *

+ * The width represents the horizontal size of the UI element. It is used + * during rendering and layout calculations to determine how much horizontal + * space the element occupies. + *

+ * + * @return The width of this UI element in pixels. + */ + int getWidth(); + + /** + * Retrieves the height of this {@code UiElement}. + *

+ * The height represents the vertical size of the UI element. It is used + * during rendering and layout calculations to determine how much vertical + * space the element occupies. + *

+ * + * @return The height of this UI element in pixels. + */ + int getHeight(); + + /** + * Retrieves the background color of this {@code UiElement}. + *

+ * The background color determines the visual fill of the UI element's area. + * If no background color has been explicitly defined, the element may render + * with a default or transparent background depending on the implementation. + *

+ * + * @return The {@link Color} instance representing the background color of + * this UI element. + */ + Color getBackground(); + +} diff --git a/src/main/java/workspace/ui/UiLabel.java b/src/main/java/workspace/ui/elements/UiLabel.java similarity index 100% rename from src/main/java/workspace/ui/UiLabel.java rename to src/main/java/workspace/ui/elements/UiLabel.java diff --git a/src/main/java/workspace/ui/UiPanel.java b/src/main/java/workspace/ui/elements/UiPanel.java similarity index 100% rename from src/main/java/workspace/ui/UiPanel.java rename to src/main/java/workspace/ui/elements/UiPanel.java diff --git a/src/main/java/workspace/ui/ViewportCompass.java b/src/main/java/workspace/ui/elements/ViewportCompass.java similarity index 100% rename from src/main/java/workspace/ui/ViewportCompass.java rename to src/main/java/workspace/ui/elements/ViewportCompass.java diff --git a/src/main/java/workspace/ui/IActionListener.java b/src/main/java/workspace/ui/event/IActionListener.java similarity index 100% rename from src/main/java/workspace/ui/IActionListener.java rename to src/main/java/workspace/ui/event/IActionListener.java diff --git a/src/main/java/workspace/ui/ISliderCallBack.java b/src/main/java/workspace/ui/event/ISliderCallBack.java similarity index 100% rename from src/main/java/workspace/ui/ISliderCallBack.java rename to src/main/java/workspace/ui/event/ISliderCallBack.java diff --git a/src/main/java/workspace/ui/MouseEvent.java b/src/main/java/workspace/ui/event/MouseEvent.java similarity index 100% rename from src/main/java/workspace/ui/MouseEvent.java rename to src/main/java/workspace/ui/event/MouseEvent.java diff --git a/src/main/java/workspace/ui/layout/Layout.java b/src/main/java/workspace/ui/layout/Layout.java index 95eaddde..edb398b6 100644 --- a/src/main/java/workspace/ui/layout/Layout.java +++ b/src/main/java/workspace/ui/layout/Layout.java @@ -1,9 +1,40 @@ package workspace.ui.layout; -import workspace.ui.UiComponent; +import workspace.ui.elements.UiElement; +/** + * Interface: Layout + * + * Defines the contract for applying a layout strategy to a given + * {@link workspace.ui.elements.UiElement}. The layout logic determines how a UI + * element's child components are positioned, sized, and arranged within its + * allocated space based on specific layout rules. This abstraction allows + * flexible and reusable layout behaviors to be implemented independently of the + * actual UI element logic. + * + *

+ * Implementations of this interface can represent various layout strategies + * such as grid-based alignment, horizontal stacking, vertical stacking, or + * custom user-defined layouts. + *

+ * + * @see workspace.ui.elements.UiElement + */ public interface Layout { - void layout(UiComponent component); + /** + * Applies the layout logic to the specified UI element and its children. + * + *

+ * This method should compute and set the positions, sizes, and other layout + * properties of the provided {@code UiElement} and its child elements + * according to the specific layout strategy implemented by the concrete + * class. + *

+ * + * @param uiElement The UI element to which the layout logic should be + * applied. + */ + void layout(UiElement uiElement); -} +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/renderer/ButtonRenderer.java b/src/main/java/workspace/ui/renderer/ButtonRenderer.java new file mode 100644 index 00000000..a2f0de48 --- /dev/null +++ b/src/main/java/workspace/ui/renderer/ButtonRenderer.java @@ -0,0 +1,44 @@ +package workspace.ui.renderer; + +import workspace.ui.Graphics; +import workspace.ui.elements.UiButton; +import workspace.ui.elements.UiElement; + +/** + * Renderer implementation for the {@code UiButton} component. + *

+ * Handles the visual representation of the button, including its background, + * text, and dimensions. + *

+ */ +public class ButtonRenderer implements Renderer { + + @Override + public void render(Graphics g, UiElement component) { + if (!(component instanceof UiButton)) + return; + + UiButton button = (UiButton) component; + String text = button.getText(); + + // Measure text width and height + int textWidth = (int) g.textWidth(text); + int textHeight = (int) (g.textAscent() + g.textDescent()); + + // Adjust button dimensions to fit text if necessary + button.setWidth(Math.max(button.getWidth(), textWidth)); + button.setHeight(Math.max(button.getHeight(), textHeight)); + + // Draw button background + g.setColor(button.getBackground()); + g.fillRect(0, 0, button.getWidth(), button.getHeight()); + + // Draw button text + if (text != null && !text.isEmpty()) { + g.setColor(button.getForeground()); + g.text(text, (button.getWidth() - textWidth) / 2, + (button.getHeight() + textHeight) / 2 - g.textDescent()); + } + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/renderer/CheckBoxRenderer.java b/src/main/java/workspace/ui/renderer/CheckBoxRenderer.java new file mode 100644 index 00000000..0f17fad2 --- /dev/null +++ b/src/main/java/workspace/ui/renderer/CheckBoxRenderer.java @@ -0,0 +1,45 @@ +package workspace.ui.renderer; + +import workspace.ui.Graphics; +import workspace.ui.border.Insets; +import workspace.ui.elements.UiCheckBox; +import workspace.ui.elements.UiElement; + +/** + * Renderer implementation for rendering the {@code UiCheckBox}. + *

+ * Responsible for drawing the checkbox, its state (checked/unchecked), and its + * label text. + *

+ */ +public class CheckBoxRenderer implements Renderer { + + @Override + public void render(Graphics g, UiElement element) { + if (!(element instanceof UiCheckBox)) + return; + + UiCheckBox checkBox = (UiCheckBox) element; + Insets insets = checkBox.getInsets(); + int offsetX = insets.getLeft(); + int offsetY = insets.getTop(); + int boxWidth = checkBox.getWidth() - insets.getHorizontalInsets(); + int boxHeight = checkBox.getHeight() - insets.getVerticalInsets(); + + // Render checkbox box + g.setColor(checkBox.getBackground()); + g.fillRect(offsetX, offsetY, boxWidth, boxHeight); + + // If checked, fill the inner box with checkmark + if (checkBox.isSelected()) { + g.setColor(checkBox.getForeground()); + g.fillRect(offsetX + 4, offsetY + 4, boxWidth - 8, boxHeight - 8); + } + + // Render text + g.setColor(checkBox.getForeground()); + g.text(checkBox.getText(), offsetX + boxWidth + 4, + offsetY + (boxHeight / 2)); + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/renderer/EditorMenuRenderer.java b/src/main/java/workspace/ui/renderer/EditorMenuRenderer.java new file mode 100644 index 00000000..6cd82153 --- /dev/null +++ b/src/main/java/workspace/ui/renderer/EditorMenuRenderer.java @@ -0,0 +1,46 @@ +package workspace.ui.renderer; + +import workspace.laf.UiConstants; +import workspace.laf.UiValues; +import workspace.ui.Graphics; +import workspace.ui.elements.UiEditorMenu; +import workspace.ui.elements.UiElement; + +/** + * Renderer implementation for the {@code UiEditorMenu} component. + *

+ * Handles drawing the background, text, and decorative lines of the editor + * menu, using styles defined in the Look and Feel (LAF) system. + *

+ */ +public class EditorMenuRenderer implements Renderer { + + @Override + public void render(Graphics g, UiElement element) { + if (!(element instanceof UiEditorMenu)) + return; + + UiEditorMenu menu = (UiEditorMenu) element; + String text = menu.getText(); + + // Draw the dark header bar + g.setColor(66, 66, 66); + g.fillRect(0, 0, g.getWidth(), 55); + + // Draw the menu background + g.setColor(menu.getBackground()); + g.fillRect(0, 0, g.getWidth(), 30); + + // Draw the menu text + if (text != null && !text.isEmpty()) { + g.setColor(menu.getForeground()); + g.textSize(UiValues.getInt(UiConstants.KEY_MENU_TEXT_SIZE)); + g.text(text, 10, 20); + } + + // Draw the bottom divider line + g.setColor(31, 31, 31); + g.fillRect(0, 28, g.getWidth(), 1); + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/renderer/LabelRenderer.java b/src/main/java/workspace/ui/renderer/LabelRenderer.java new file mode 100644 index 00000000..e7d5b6db --- /dev/null +++ b/src/main/java/workspace/ui/renderer/LabelRenderer.java @@ -0,0 +1,44 @@ +package workspace.ui.renderer; + +import workspace.ui.Graphics; +import workspace.ui.border.Insets; +import workspace.ui.elements.UiElement; +import workspace.ui.elements.UiLabel; + +/** + * Renderer implementation for rendering {@code UiLabel} components. + *

+ * Handles the drawing of the label's background, text, and ensures proper + * alignment. + *

+ */ +public class LabelRenderer implements Renderer { + + @Override + public void render(Graphics g, UiElement element) { + if (!(element instanceof UiLabel)) + return; + + UiLabel label = (UiLabel) element; + String title = label.getTitle(); + if (title == null || title.isEmpty()) + return; + + Insets insets = element.getInsets(); + int x = insets.getLeft(); + int y = insets.getTop(); + + // Calculate text dimensions + float textWidth = g.textWidth(title); + float textHeight = g.textAscent() + g.textDescent(); + + // Render background + g.setColor(label.getBackground()); + g.fillRect(x, y, textWidth, textHeight); + + // Render text + g.setColor(label.getForeground()); + g.text(title, x, y + g.textAscent()); + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/renderer/PanelRenderer.java b/src/main/java/workspace/ui/renderer/PanelRenderer.java new file mode 100644 index 00000000..beffc9bd --- /dev/null +++ b/src/main/java/workspace/ui/renderer/PanelRenderer.java @@ -0,0 +1,45 @@ +package workspace.ui.renderer; + +import workspace.ui.Graphics; +import workspace.ui.border.Insets; +import workspace.ui.elements.UiElement; + +/** + * Default implementation of the {@link Renderer} interface for rendering a + * standard panel. + *

+ * This class provides a simple rendering strategy by drawing a solid background + * rectangle for a given {@code UiElement} based on its background color and + * insets. It ensures that rendering respects the defined padding or margins + * (insets) and only draws if a background color is set. + *

+ */ +public class PanelRenderer implements Renderer { + + /** + * Renders the panel's background onto the given graphics context. + *

+ * The rendering accounts for the insets (margins or padding) defined by the + * element's layout and only fills the visible area with the panel's + * background color if it is set. + *

+ * + * @param g The {@link Graphics} object used for rendering. + * @param element The {@link UiElement} representing the UI component to + * render. + */ + @Override + public void render(Graphics g, UiElement element) { + Insets insets = element.getInsets(); + int x = insets.getLeft(); + int y = insets.getTop(); + int width = element.getWidth() - insets.getHorizontalInsets(); + int height = element.getHeight() - insets.getVerticalInsets(); + + if (element.getBackground() != null) { + g.setColor(element.getBackground()); + g.fillRect(x, y, width, height); + } + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/renderer/Renderer.java b/src/main/java/workspace/ui/renderer/Renderer.java new file mode 100644 index 00000000..13a1347d --- /dev/null +++ b/src/main/java/workspace/ui/renderer/Renderer.java @@ -0,0 +1,42 @@ +package workspace.ui.renderer; + +import workspace.ui.Graphics; +import workspace.ui.elements.UiElement; + +/** + * Interface: Renderer + * + * Defines the contract for rendering UI elements onto a provided Graphics + * context. This abstraction allows different rendering strategies to be + * implemented, promoting flexibility and loose coupling in the rendering logic. + * Implementations of this interface are responsible for visually representing + * {@link workspace.ui.elements.UiElement} instances, including their layout, + * visual properties, and child components. + * + *

+ * The Renderer allows for customizable rendering implementations, such as 2D, + * 3D, or other rendering pipelines without requiring changes to the underlying + * UI component logic. + *

+ * + * @see workspace.ui.elements.UiElement + * @see workspace.ui.Graphics + */ +public interface Renderer { + + /** + * Renders a given UI element on the specified Graphics context. + * + *

+ * Implementations of this method will handle drawing the provided + * {@code UiElement} onto the rendering surface represented by the given + * {@code Graphics} context. This includes handling UI styles, dimensions, + * borders, backgrounds, or any visual representation logic. + *

+ * + * @param g The Graphics context where the rendering will occur. + * @param element The UiElement instance to render. + */ + void render(Graphics g, UiElement element); + +} \ No newline at end of file From 6cf8a14274b887787543526ab34b70683793c498 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 12:43:57 +0100 Subject: [PATCH 10/15] Refactor: Transition UI Framework to Abstract, Flexible Design --- .../java/workspace/ui/elements/UiButton.java | 155 +++++++++---- .../workspace/ui/elements/UiCheckBox.java | 217 +++++++++++------- .../workspace/ui/elements/UiEditorMenu.java | 85 +++---- .../java/workspace/ui/elements/UiLabel.java | 83 ++++--- .../java/workspace/ui/elements/UiPanel.java | 37 ++- .../ui/elements/ViewportCompass.java | 4 +- .../workspace/ui/event/IActionListener.java | 42 +++- .../workspace/ui/event/ISliderCallBack.java | 4 +- .../java/workspace/ui/event/MouseEvent.java | 100 ++++++-- 9 files changed, 479 insertions(+), 248 deletions(-) diff --git a/src/main/java/workspace/ui/elements/UiButton.java b/src/main/java/workspace/ui/elements/UiButton.java index dc0e6a3e..77c7b40f 100644 --- a/src/main/java/workspace/ui/elements/UiButton.java +++ b/src/main/java/workspace/ui/elements/UiButton.java @@ -1,53 +1,108 @@ -package workspace.ui; +package workspace.ui.elements; +import workspace.ui.UiComponent; +import workspace.ui.event.IActionListener; +import workspace.ui.renderer.ButtonRenderer; + +/** + * Represents a button component in the UI. + *

+ * A button can display text and execute an associated action when clicked. + * Rendering is handled by a dedicated renderer, allowing for customizable + * visual styles. + *

+ */ public class UiButton extends UiComponent { - private String text; - - private IActionListener actionListener; - - public UiButton(String text) { - this(text, 0, 0, 0, 0); - } - - public UiButton(String text, int x, int y, int width, int height) { - this.text = text; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - public void onMouseClicked(int x, int y) { - super.onMouseClicked(x, y); - if (actionListener == null) - return; - actionListener.onActionPerformed(); - } - - @Override - public void onDraw(Graphics g) { - width = (int) g.textWidth(text); - g.setColor(background); - g.fillRect(0, 0, g.textWidth(text), g.textAscent() + g.textDescent()); - g.setColor(foreground); - g.text(text, 0, g.getTextSize()); - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public IActionListener getActionListener() { - return actionListener; - } - - public void setActionListener(IActionListener actionListener) { - this.actionListener = actionListener; - } - -} + /** + * The text displayed on the button. + */ + private String text; + + /** + * The action listener for click events. + */ + private IActionListener actionListener; + + /** + * Constructs a new {@code UiButton} with the specified text. + * + * @param text The text to display on the button. + */ + public UiButton(String text) { + this(text, 0, 0, 0, 0); + } + + /** + * Constructs a new {@code UiButton} with the specified text and dimensions. + * + * @param text The text to display on the button. + * @param x The x-coordinate of the button. + * @param y The y-coordinate of the button. + * @param width The width of the button. + * @param height The height of the button. + */ + public UiButton(String text, int x, int y, int width, int height) { + this.text = text; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + setRenderer(new ButtonRenderer()); + } + + /** + * Handles mouse click events for the button. + *

+ * If the button has an associated action listener, its + * {@link IActionListener#onActionPerformed()} method is invoked. + *

+ * + * @param x The x-coordinate of the mouse click. + * @param y The y-coordinate of the mouse click. + */ + @Override + public void onMouseClicked(int x, int y) { + super.onMouseClicked(x, y); + if (actionListener != null) { + actionListener.onActionPerformed(); + } + } + + /** + * Gets the text displayed on the button. + * + * @return The current text of the button. + */ + public String getText() { + return text; + } + + /** + * Sets the text to display on the button. + * + * @param text The new text to display. + */ + public void setText(String text) { + this.text = text; + } + + /** + * Gets the action listener associated with the button. + * + * @return The current action listener, or {@code null} if none is set. + */ + public IActionListener getActionListener() { + return actionListener; + } + + /** + * Sets the action listener for the button. + * + * @param actionListener The action listener to associate with the button. + */ + public void setActionListener(IActionListener actionListener) { + this.actionListener = actionListener; + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/elements/UiCheckBox.java b/src/main/java/workspace/ui/elements/UiCheckBox.java index 5056c465..c4a4c366 100644 --- a/src/main/java/workspace/ui/elements/UiCheckBox.java +++ b/src/main/java/workspace/ui/elements/UiCheckBox.java @@ -1,80 +1,141 @@ -package workspace.ui; - -import workspace.ui.border.Insets; - +package workspace.ui.elements; + +import workspace.ui.Graphics; +import workspace.ui.UiComponent; +import workspace.ui.event.IActionListener; +import workspace.ui.renderer.CheckBoxRenderer; + +/** + * A UI CheckBox component that can be toggled (selected/deselected) and + * supports click handling. + *

+ * This class represents a checkbox UI element with a label, click interaction, + * and customizable rendering. + *

+ */ public class UiCheckBox extends UiComponent { - protected boolean selected; - - protected String text; - - protected IActionListener actionListener; - - public UiCheckBox(String text) { - this.text = text; - this.width = 13; - this.height = 13; - } - - @Override - public void onDraw(Graphics g) { - Insets insets = getInsets(); - int offsetX = getWidth() / 6; - int offsetY = getHeight() / 6; - - g.setColor(background); - g.fillRect( - insets.left, insets.top, width - insets.getWidth() - 1, - height - insets.getHeight() - 1 - ); - - g.setColor(foreground); - g.text(text, width + 5, g.getTextSize()); - - if (!selected) - return; - - g.setColor(foreground); - g.fillRect( - insets.left + offsetX, insets.top + offsetY, - width - insets.getWidth() - 1 - (2 * offsetX), - height - insets.getHeight() - 1 - (2 * offsetY) - ); - } - - @Override - public void onMouseClicked(int x, int y) { - super.onMouseClicked(x, y); - setSelected(!isSelected()); - } - - public boolean isSelected() { - return selected; - } - - public void setSelected(boolean selected) { - boolean oldValue = this.selected; - if (oldValue == selected) - return; - this.selected = selected; - if (actionListener != null) - actionListener.onActionPerformed(); - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public IActionListener getActionListener() { - return actionListener; - } - - public void setActionListener(IActionListener actionListener) { - this.actionListener = actionListener; - } - -} + /** + * Checkbox state. + */ + private boolean selected; + + /** + * Label text for the checkbox. + */ + private String text; + + /** + * Listener for click events. + */ + private IActionListener actionListener; + + private final CheckBoxRenderer renderer; + + /** + * Constructs a new {@code UiCheckBox} with the provided label text. + *

+ * Initializes default dimensions and sets up the checkbox renderer. + *

+ * + * @param text The label text to display next to the checkbox. + */ + public UiCheckBox(String text) { + this.text = text; + this.width = 20; + this.height = 20; + this.renderer = new CheckBoxRenderer(); + } + + /** + * Handles rendering for this checkbox component. + * + * @param g The {@link Graphics} context to draw on. + */ + @Override + public void renderSelf(Graphics g) { + renderer.render(g, this); + } + + /** + * Handles mouse click interactions. Toggles the selection state when clicked. + * + * @param x X-coordinate of the mouse click. + * @param y Y-coordinate of the mouse click. + */ + @Override + public void onMouseClicked(int x, int y) { + super.onMouseClicked(x, y); + toggleSelection(); + } + + /** + * Toggles the selection state of this checkbox and invokes the action + * listener, if any. + */ + private void toggleSelection() { + setSelected(!selected); + } + + /** + * Checks if the checkbox is currently selected. + * + * @return {@code true} if the checkbox is selected; {@code false} otherwise. + */ + public boolean isSelected() { + return selected; + } + + /** + * Updates the selection state of the checkbox and notifies listeners about + * the change. + * + * @param selected The new selection state to set. + */ + public void setSelected(boolean selected) { + if (this.selected == selected) { + return; // No state change + } + this.selected = selected; + if (actionListener != null) { + actionListener.onActionPerformed(); + } + } + + /** + * Gets the text label associated with the checkbox. + * + * @return The current text label. + */ + public String getText() { + return text; + } + + /** + * Updates the text label of the checkbox. + * + * @param text The new text label. + */ + public void setText(String text) { + this.text = text; + } + + /** + * Sets an action listener for handling click events on this checkbox. + * + * @param listener The action listener to set. + */ + public void setActionListener(IActionListener listener) { + this.actionListener = listener; + } + + /** + * Retrieves the currently set action listener. + * + * @return The action listener currently assigned to this checkbox. + */ + public IActionListener getActionListener() { + return actionListener; + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/elements/UiEditorMenu.java b/src/main/java/workspace/ui/elements/UiEditorMenu.java index 71bb65ac..dc86d8fb 100644 --- a/src/main/java/workspace/ui/elements/UiEditorMenu.java +++ b/src/main/java/workspace/ui/elements/UiEditorMenu.java @@ -1,47 +1,48 @@ -package workspace.ui; +package workspace.ui.elements; import workspace.laf.UiConstants; import workspace.laf.UiValues; - +import workspace.ui.UiComponent; +import workspace.ui.renderer.EditorMenuRenderer; + +/** + * Represents an editor menu component in the UI. + *

+ * The menu displays a background and customizable text, styled using values + * from the Look and Feel (LAF) system. Rendering is delegated to a dedicated + * renderer for modularity. + *

+ */ public class UiEditorMenu extends UiComponent { - private String text; - - public UiEditorMenu() { - setText(""); - setForeground(UiValues.getColor(UiConstants.KEY_MENU_FOREGROUND_COLOR)); - setBackground(UiValues.getColor(UiConstants.KEY_MENU_BACKGROUND_COLOR)); - } - - @Override - public void onDraw(Graphics g) { - g.setColor(66, 66, 66); - g.fillRect(0, 0, g.getWidth(), 55); - - drawBackground(g); - drawText(g); - - g.setColor(31, 31, 31); - g.fillRect(0, 28, g.getWidth(), 1); - } - - private void drawBackground(Graphics g) { - g.setColor(getBackground()); - g.fillRect(0, 0, g.getWidth(), 30); - } - - private void drawText(Graphics g) { - g.setColor(getForeground()); - g.textSize(UiValues.getInt(UiConstants.KEY_MENU_TEXT_SIZE)); - g.text(getText(), 10, 20); - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - -} + private String text; // The menu's display text. + + /** + * Constructs a new {@code UiEditorMenu} with default styles. + */ + public UiEditorMenu() { + setText(""); + setForeground(UiValues.getColor(UiConstants.KEY_MENU_FOREGROUND_COLOR)); + setBackground(UiValues.getColor(UiConstants.KEY_MENU_BACKGROUND_COLOR)); + setRenderer(new EditorMenuRenderer()); + } + + /** + * Gets the text displayed in this menu. + * + * @return The current text of the menu. + */ + public String getText() { + return text; + } + + /** + * Sets the text displayed in this menu. + * + * @param text The new text to display. + */ + public void setText(String text) { + this.text = text; + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/elements/UiLabel.java b/src/main/java/workspace/ui/elements/UiLabel.java index 899d511d..1f56ebdb 100644 --- a/src/main/java/workspace/ui/elements/UiLabel.java +++ b/src/main/java/workspace/ui/elements/UiLabel.java @@ -1,38 +1,49 @@ -package workspace.ui; - +package workspace.ui.elements; + +import workspace.ui.UiComponent; +import workspace.ui.renderer.LabelRenderer; + +/** + * A simple UI label component for displaying text. + *

+ * The label supports customizable text, background, and foreground colors. + * Rendering logic is delegated to a dedicated renderer for consistency with the + * framework's modular design. + *

+ */ public class UiLabel extends UiComponent { - private String title; - - public void draw(Graphics g) { - g.setColor(background); - g.fillRect(x, y, g.textWidth(title), g.textAscent() + g.textDescent()); - g.setColor(foreground); - g.text(title, x, y + g.getTextSize()); - } - - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; - } - - public int getY() { - return y; - } - - public void setY(int y) { - this.y = y; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - -} + /** + * The text displayed by the label. + */ + private String title; + + /** + * Constructs a new {@code UiLabel} with the given title. + * + * @param title The text to display on the label. + */ + public UiLabel(String title) { + this.title = title; + setRenderer(new LabelRenderer()); + } + + /** + * Gets the title (text) displayed by this label. + * + * @return The current title of the label. + */ + public String getTitle() { + return title; + } + + /** + * Sets the title (text) of this label. + * + * @param title The new text to display on the label. + */ + public void setTitle(String title) { + this.title = title; + } + +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/elements/UiPanel.java b/src/main/java/workspace/ui/elements/UiPanel.java index a30f9ad0..9fdde2cd 100644 --- a/src/main/java/workspace/ui/elements/UiPanel.java +++ b/src/main/java/workspace/ui/elements/UiPanel.java @@ -1,17 +1,32 @@ -package workspace.ui; +package workspace.ui.elements; -import workspace.ui.border.Insets; +import workspace.ui.UiComponent; +import workspace.ui.renderer.PanelRenderer; +/** + * Represents a simple UI panel component that serves as a container for other + * UI elements. + *

+ * This class extends {@link UiComponent} and initializes itself with the + * default rendering logic provided by {@link PanelRenderer}. The panel + * is responsible for managing its background rendering and acts as a building + * block for creating layouts and grouping child components within the user + * interface. + *

+ */ public class UiPanel extends UiComponent { - @Override - public void onDraw(Graphics g) { - Insets insets = getInsets(); - g.setColor(getBackground()); - g.fillRect( - insets.left, insets.right, width - insets.getWidth(), - height - insets.getHeight() - ); - } + /** + * Constructs a new {@code UiPanel} instance and sets its renderer to the + * default panel renderer. + *

+ * The {@link PanelRenderer} handles the rendering of this panel's + * visual appearance, including painting its background and respecting any + * defined insets or layout configuration. + *

+ */ + public UiPanel() { + setRenderer(new PanelRenderer()); + } } diff --git a/src/main/java/workspace/ui/elements/ViewportCompass.java b/src/main/java/workspace/ui/elements/ViewportCompass.java index b143ea99..7a6f9c50 100644 --- a/src/main/java/workspace/ui/elements/ViewportCompass.java +++ b/src/main/java/workspace/ui/elements/ViewportCompass.java @@ -1,4 +1,4 @@ -package workspace.ui; +package workspace.ui.elements; import math.Mathf; import math.Vector3f; @@ -11,6 +11,8 @@ import mesh.modifier.TranslateModifier; import workspace.laf.UiConstants; import workspace.laf.UiValues; +import workspace.ui.Graphics; +import workspace.ui.UiComponent; /** * Represents a simple viewport compass in the upper-right corner of the mesh diff --git a/src/main/java/workspace/ui/event/IActionListener.java b/src/main/java/workspace/ui/event/IActionListener.java index 7dd1b5c4..1bf31956 100644 --- a/src/main/java/workspace/ui/event/IActionListener.java +++ b/src/main/java/workspace/ui/event/IActionListener.java @@ -1,7 +1,43 @@ -package workspace.ui; +package workspace.ui.event; +/** + * Represents a listener for action events in the UI framework. + *

+ * Implementations of this interface can be used to handle user interactions, + * such as button clicks, menu selections, or other actions triggered within the + * user interface. + *

+ * + *

+ * To use this interface, implement the {@code onActionPerformed()} method and + * associate the listener with a UI component that supports actions, such as a + * button or menu item. + *

+ * + *
+ * Example usage:
+ * 
+ * UiButton button = new UiButton("Click Me");
+ * button.setActionListener(new IActionListener() {
+ *     @Override
+ *     public void onActionPerformed() {
+ *         System.out.println("Button clicked!");
+ *     }
+ * });
+ * 
+ *

+ */ public interface IActionListener { - void onActionPerformed(); + /** + * Called when an action is performed. + *

+ * This method is invoked in response to a user interaction with a UI + * component, such as clicking a button or selecting a menu item. + * Implementations should define the behavior to execute when the action + * occurs. + *

+ */ + void onActionPerformed(); -} +} \ No newline at end of file diff --git a/src/main/java/workspace/ui/event/ISliderCallBack.java b/src/main/java/workspace/ui/event/ISliderCallBack.java index d7d18d47..3c417fa8 100644 --- a/src/main/java/workspace/ui/event/ISliderCallBack.java +++ b/src/main/java/workspace/ui/event/ISliderCallBack.java @@ -1,7 +1,7 @@ -package workspace.ui; +package workspace.ui.event; public interface ISliderCallBack { - void valueChanged(float value); + void valueChanged(float value); } diff --git a/src/main/java/workspace/ui/event/MouseEvent.java b/src/main/java/workspace/ui/event/MouseEvent.java index bacbc120..086e781a 100644 --- a/src/main/java/workspace/ui/event/MouseEvent.java +++ b/src/main/java/workspace/ui/event/MouseEvent.java @@ -1,37 +1,87 @@ -package workspace.ui; +package workspace.ui.event; +/** + * Represents a mouse event, containing information about the current and + * previous mouse positions. + *

+ * This class is used to encapsulate the state of the mouse during an event, + * such as mouse movement or mouse clicks. It provides details about the mouse's + * current coordinates as well as its previous position. + *

+ * + *
+ * Example usage:
+ * 
+ * MouseEvent event = new MouseEvent(100, 200, 90, 180);
+ * int currentX = event.getMouseX();
+ * int previousX = event.getPreviousMouseX();
+ * 
+ */ public class MouseEvent { - private int mouseX; + /** The current X-coordinate of the mouse. */ + private final int mouseX; - private int mouseY; + /** The current Y-coordinate of the mouse. */ + private final int mouseY; - private int previousMouseX; + /** The previous X-coordinate of the mouse. */ + private final int previousMouseX; - private int previousMouseY; + /** The previous Y-coordinate of the mouse. */ + private final int previousMouseY; - public MouseEvent(int mouseX, int mouseY, int previousMouseX, - int previousMouseY) { - this.mouseX = mouseX; - this.mouseY = mouseY; - this.previousMouseX = previousMouseX; - this.previousMouseY = previousMouseY; - } + /** + * Constructs a new {@code MouseEvent} with the specified current and previous + * mouse coordinates. + * + * @param mouseX The current X-coordinate of the mouse. + * @param mouseY The current Y-coordinate of the mouse. + * @param previousMouseX The previous X-coordinate of the mouse. + * @param previousMouseY The previous Y-coordinate of the mouse. + */ + public MouseEvent(int mouseX, int mouseY, int previousMouseX, + int previousMouseY) { + this.mouseX = mouseX; + this.mouseY = mouseY; + this.previousMouseX = previousMouseX; + this.previousMouseY = previousMouseY; + } - public int getMouseX() { - return mouseX; - } + /** + * Gets the current X-coordinate of the mouse. + * + * @return The current X-coordinate. + */ + public int getMouseX() { + return mouseX; + } - public int getMouseY() { - return mouseY; - } + /** + * Gets the current Y-coordinate of the mouse. + * + * @return The current Y-coordinate. + */ + public int getMouseY() { + return mouseY; + } - public int getPreviousMouseX() { - return previousMouseX; - } + /** + * Gets the previous X-coordinate of the mouse. + * + * @return The previous X-coordinate. + */ + public int getPreviousMouseX() { + return previousMouseX; + } - public int getPreviousMouseY() { - return previousMouseY; - } + /** + * Gets the previous Y-coordinate of the mouse. + * + * @return The previous Y-coordinate. + */ + public int getPreviousMouseY() { + return previousMouseY; + } -} +} \ No newline at end of file From afd4c72211e5cc847045baeb697eafcfbd11ed78 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 13:23:39 +0100 Subject: [PATCH 11/15] feat: add comprehensive lighting system to scene package - Introduced `Light` interface to define common properties and behavior for all light types. - Added `LightRenderer` interface to decouple rendering logic for different light types. - Implemented `DirectionalLight` for uniform parallel lighting, typically used for simulating sunlight. - Implemented `PointLight` for omnidirectional light sources. - Implemented `Spotlight` for directional lighting with limited cone angles. - Added `LightType` enum to categorize and identify light types. This commit establishes a flexible and extensible lighting system, enabling dynamic light management and rendering in the scene graph. --- .../java/scene/light/DirectionalLight.java | 200 +++++++++++++ src/main/java/scene/light/Light.java | 49 ++++ src/main/java/scene/light/LightRenderer.java | 66 +++++ src/main/java/scene/light/LightType.java | 21 ++ src/main/java/scene/light/PointLight.java | 210 ++++++++++++++ src/main/java/scene/light/SpotLight.java | 268 ++++++++++++++++++ 6 files changed, 814 insertions(+) create mode 100644 src/main/java/scene/light/DirectionalLight.java create mode 100644 src/main/java/scene/light/Light.java create mode 100644 src/main/java/scene/light/LightRenderer.java create mode 100644 src/main/java/scene/light/LightType.java create mode 100644 src/main/java/scene/light/PointLight.java create mode 100644 src/main/java/scene/light/SpotLight.java diff --git a/src/main/java/scene/light/DirectionalLight.java b/src/main/java/scene/light/DirectionalLight.java new file mode 100644 index 00000000..29ed5bf6 --- /dev/null +++ b/src/main/java/scene/light/DirectionalLight.java @@ -0,0 +1,200 @@ +package scene.light; + +import math.Color; +import math.Vector3f; + +/** + * Represents a directional light source in a 3D scene. + * + *

+ * A directional light simulates light emitted from a distant source, such as + * the sun or moon. Unlike point lights or spotlights, directional lights have + * no specific position, and their light rays travel in a uniform direction + * throughout the scene. This makes them ideal for creating consistent lighting + * over large areas. + *

+ * + *
+ * Key characteristics of a directional light:
+ * - The light's direction is defined by a normalized vector.
+ * - It emits light uniformly in the specified direction, without attenuation 
+ *   (intensity does not decrease with distance).
+ * - It is commonly used to simulate natural light sources like sunlight during
+ *   the day or moonlight at night.
+ * 
+ * + * This class provides methods to configure the light's direction, color, and + * intensity, as well as integration with rendering systems via the + * {@link LightRenderer}. + */ +public class DirectionalLight implements Light { + + /** + * The color of the light emitted by the directional light source. + */ + private Color color; + + /** + * The direction of the light source. + */ + private Vector3f direction; + + /** + * The intensity of the light emitted by the directional light source. + */ + private float intensity; + + /** + * Creates a new DirectionalLight instance with default settings. + * + *

+ * This constructor initializes the light with the following defaults: - + * Color: White light. RGB(255, 255, 255) - Direction: A downward-facing + * vector (0, 1, 0), simulating overhead light. - Intensity: 1.0 (full + * strength). + *

+ */ + public DirectionalLight() { + this(Color.WHITE, new Vector3f(0, 1, 0), 1.0f); + } + + /** + * Creates a new DirectionalLight instance. + * + * @param color The color of the light emitted by the directional light + * source. Represents the RGB values of the light's color. + * This parameter cannot be null. + * @param direction The direction of the light source. This vector determines + * the direction in which the light rays travel. The provided + * vector is automatically normalized during construction, + * ensuring the direction's magnitude is always 1. This + * parameter cannot be null. + * @param intensity The intensity of the light emitted by the directional + * light source. This value must be non-negative. + * + * @throws IllegalArgumentException if the direction or color is null, or if + * the intensity is negative. + */ + public DirectionalLight(Color color, Vector3f direction, float intensity) { + setColor(color); + setDirection(direction); + setIntensity(intensity); + } + + /** + * Gets the direction of the light source. + * + * @return The direction of the light source. + * @see #setDirection(Vector3f) + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction of the directional light source. + * + * The provided vector is normalized to ensure the light's direction always + * has a magnitude of 1, which maintains consistent light behavior. This + * method validates that the input is not null to avoid runtime errors. + * + * @param direction The new direction vector for the light source. This vector + * defines the direction in which the light rays travel. + * + * @throws IllegalArgumentException if the provided direction vector is null. + */ + public void setDirection(Vector3f direction) { + if (direction == null) + throw new IllegalArgumentException("Direction cannot be null."); + this.direction = direction.normalize(); + } + + /** + * Gets the color of the light emitted by the directional light source. + * + * @return The color of the light. + * @see #setColor(Color) + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the directional light source. + * + * This method updates the light's emitted color. It validates that the + * provided color is not null to ensure the light's color is always valid. + * + * @param color The new color of the light to set. Represents the RGB values + * of the light's color. + * + * @throws IllegalArgumentException if the provided color is null. + */ + public void setColor(Color color) { + if (color == null) + throw new IllegalArgumentException("Color cannot be null."); + this.color = color; + } + + /** + * Gets the intensity of the light emitted by the directional light source. + * + * @return The intensity of the light. + * @see #setIntensity(float) + */ + public float getIntensity() { + return intensity; + } + + /** + * Sets the intensity of the light emitted by the directional light source. + * + * The intensity value determines how bright the light appears in the scene. + * This method ensures that the value is non-negative, as negative intensity + * does not make logical sense in this context. + * + * @param intensity The new intensity value to set for the light source. Must + * be non-negative to represent valid light brightness. + * + * @throws IllegalArgumentException if the provided intensity is negative. + */ + public void setIntensity(float intensity) { + if (intensity < 0) + throw new IllegalArgumentException("Intensity must be non-negative."); + this.intensity = intensity; + } + + /** + * Gets the type of the light source. + * + * @return The type of the light source, which is `LightType.DIRECTIONAL`. + */ + @Override + public LightType getType() { + return LightType.DIRECTIONAL; + } + + /** + * Renders the directional light source using the provided renderer. + * + * @param renderer The renderer to use for rendering the light source. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Provides a string representation of this directional light instance for + * debugging. + * + * @return String describing the current state of the directional light. + */ + @Override + public String toString() { + return "DirectionalLight [color=" + color + ", direction=" + direction + + ", intensity=" + intensity + "]"; + } + +} \ No newline at end of file diff --git a/src/main/java/scene/light/Light.java b/src/main/java/scene/light/Light.java new file mode 100644 index 00000000..002ffddd --- /dev/null +++ b/src/main/java/scene/light/Light.java @@ -0,0 +1,49 @@ +package scene.light; + +import math.Color; + +/** + * Interface for defining light sources within a 3D scene. + * + *

+ * This interface serves as a contract for all light types (e.g., PointLight, + * DirectionalLight, SpotLight) by defining essential behaviors and properties + * that any light source should possess. It provides mechanisms to query a + * light's type, retrieve its color, and delegate rendering logic to a given + * renderer. + *

+ */ +public interface Light { + + /** + * Gets the color of the light emitted by the light source. + * + * @return The {@link Color} object representing the light's color. The color + * should define the RGB components that determine the light's hue and + * saturation. + */ + Color getColor(); + + /** + * Gets the type of the light source. + * + * @return The {@link LightType} that identifies the specific type of light + * (e.g., POINT, DIRECTIONAL, or SPOT) this instance represents. + */ + LightType getType(); + + /** + * Gets the light source using the provided renderer to draw the light's + * effects. + * + * This method allows the implementation to delegate rendering logic to the + * given {@link LightRenderer}. The rendering logic could involve adding + * effects like shadows, light rays, or other visual representations specific + * to the light's type. + * + * @param renderer The {@link LightRenderer} implementation responsible for + * rendering this light's effects in the scene. + */ + void render(LightRenderer renderer); + +} \ No newline at end of file diff --git a/src/main/java/scene/light/LightRenderer.java b/src/main/java/scene/light/LightRenderer.java new file mode 100644 index 00000000..3490d213 --- /dev/null +++ b/src/main/java/scene/light/LightRenderer.java @@ -0,0 +1,66 @@ +package scene.light; + +/** + * Interface for rendering various light sources in a 3D scene. + *

+ * This interface establishes a contract for rendering different types of light + * sources in a 3D environment. It provides specific rendering methods for each + * type of light, such as {@link PointLight}, {@link DirectionalLight}, and + * {@link SpotLight}. Implementations of this interface handle the actual + * rendering logic for these light types within a 3D graphics or game engine. + *

+ */ +public interface LightRenderer { + + /** + * Renders a generic light source. + *

+ * This method is a catch-all for rendering any light source that implements + * the {@link Light} interface. Specific rendering logic for the light type + * may be determined by the implementation. + *

+ * + * @param light The light source to render. Must not be null. + */ + void render(Light light); + + /** + * Renders a spotlight. + *

+ * This method is responsible for rendering a spotlight with specific + * directionality, cone angles, and attenuation effects. Spotlights are used + * to simulate focused beams of light, such as those from flashlights, lamps, + * or theater lighting. + *

+ * + * @param light The spotlight to render. Must not be null. + */ + void render(SpotLight light); + + /** + * Renders a point light source. + *

+ * This method handles the rendering of a point light, which emits light + * uniformly in all directions from a single point in 3D space. Point lights + * are commonly used to simulate small localized light sources such as light + * bulbs or torches. + *

+ * + * @param light The point light source to render. Must not be null. + */ + void render(PointLight light); + + /** + * Renders a directional light source. + *

+ * This method handles rendering for a directional light, which simulates + * light coming from a distant, uniform direction (e.g., sunlight or + * moonlight). Directional lights are ideal for simulating natural light + * sources that do not have an attenuation effect based on distance. + *

+ * + * @param light The directional light source to render. Must not be null. + */ + void render(DirectionalLight light); + +} \ No newline at end of file diff --git a/src/main/java/scene/light/LightType.java b/src/main/java/scene/light/LightType.java new file mode 100644 index 00000000..051300a9 --- /dev/null +++ b/src/main/java/scene/light/LightType.java @@ -0,0 +1,21 @@ +package scene.light; + +/** + * Enum representing different types of lights. + * + * This enum defines the three primary types of lights commonly used in 3D + * graphics: + * + *
+ * - POINT: A point light emits light uniformly in all directions.
+ * - DIRECTIONAL: A directional light emits light in parallel rays from 
+ *   a specific direction.
+ * - SPOT: A spotlight emits light in a cone shape, with a specific 
+ *   direction and angle.
+ * 
+ */ +public enum LightType { + + POINT, DIRECTIONAL, SPOT + +} diff --git a/src/main/java/scene/light/PointLight.java b/src/main/java/scene/light/PointLight.java new file mode 100644 index 00000000..f37a81df --- /dev/null +++ b/src/main/java/scene/light/PointLight.java @@ -0,0 +1,210 @@ +package scene.light; + +import math.Color; +import math.Vector3f; + +/** + * Represents a point light source in a 3D scene. + * + *

+ * A point light simulates a light-emitting point in space, radiating light + * uniformly in all directions. It is characterized by its position, color, + * intensity, and range. This class is ideal for simulating localized light + * sources such as lightbulbs, torches, or other small light emitters in a 3D + * environment. + *

+ * + *
+ * Key Characteristics of a point light:
+ * Position: A 3D vector representing the spatial location of the light in 
+ * the scene.
+ * Color: The color of light the point light emits. Represented by an instance
+ * of {@link math.Color}.
+ * Intensity: A non-negative float value that defines how bright the light
+ * appears.
+ * Range: The maximum distance at which the light's effect is visible, beyond
+ * which the light has no influence.
+ * 
+ * + * Usage: This class provides methods to dynamically configure light properties, + * such as changing the light's intensity, range, or color at runtime. + * Integration with rendering systems can be accomplished via the + * {@link LightRenderer}. + */ +public class PointLight implements Light { + + /** + * The color of the light emitted by the point light source. + */ + private Color color; + + /** + * The 3D position of the point light source within the scene. + */ + private Vector3f position; + + /** + * The intensity of the light emitted by the point light source. + */ + private float intensity; + + /** + * The maximum distance at which the light's effect can influence objects. + */ + private float range; + + /** + * Creates a new PointLight instance with default settings. + *

+ * This constructor initializes the point light with the following default + * values: - Color: White (RGB(255, 255, 255)). - Position: (0, 0, 0). - + * Intensity: 1.0. - Range: 10.0. + *

+ */ + public PointLight() { + this(Color.WHITE, new Vector3f(0, 0, 0), 1.0f, 10.0f); + } + + /** + * Creates a new PointLight instance with specified parameters. + * + * @param color The color of the light. Must not be null. + * @param position The 3D position of the light source in the scene. Must not + * be null. + * @param intensity The intensity of the light. Must be a non-negative value. + * @param range The maximum distance of the light's effect. Must be + * non-negative. + * + * @throws IllegalArgumentException if any argument is invalid (e.g., null + * values or negative numbers). + */ + public PointLight(Color color, Vector3f position, float intensity, + float range) { + setColor(color); + setPosition(position); + setIntensity(intensity); + setRange(range); + } + + /** + * Gets the maximum range at which the light's effect is felt. + * + * @return The range of the light's effect in world units. + */ + public float getRange() { + return range; + } + + /** + * Sets the maximum range of the light's influence in the scene. + * + * @param range The new range value. Must be non-negative. + * @throws IllegalArgumentException if the provided range is less than 0. + */ + public void setRange(float range) { + if (range < 0) { + throw new IllegalArgumentException("Range must be non-negative."); + } + this.range = range; + } + + /** + * Gets the current intensity of the light. + * + * @return The intensity value, a non-negative float. + */ + public float getIntensity() { + return intensity; + } + + /** + * Sets the intensity of the light source. + * + * @param intensity The new intensity value to apply. Must be non-negative. + * @throws IllegalArgumentException if intensity is less than 0. + */ + public void setIntensity(float intensity) { + if (intensity < 0) { + throw new IllegalArgumentException("Intensity must be non-negative."); + } + this.intensity = intensity; + } + + /** + * Gets the color of the light emitted by the point light source. + * + * @return The current {@link math.Color} of the point light. + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the light source. + * + * @param color The new color value for the light source. Must not be null. + * @throws IllegalArgumentException if color is null. + */ + public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } + this.color = color; + } + + /** + * Gets the 3D position of the light source. + * + * @return The current position of the light as a {@link math.Vector3f}. + */ + public Vector3f getPosition() { + return position; + } + + /** + * Sets the 3D position of the light source within the 3D scene. + * + * @param position The new position value to set. Must not be null. + * @throws IllegalArgumentException if position is null. + */ + public void setPosition(Vector3f position) { + if (position == null) { + throw new IllegalArgumentException("Position cannot be null."); + } + this.position = position; + } + + /** + * Gets the type of light. + * + * @return The type of the light, represented as `LightType.POINT`. + */ + @Override + public LightType getType() { + return LightType.POINT; + } + + /** + * Renders this point light source using the provided renderer. + * + * @param renderer The renderer responsible for rendering the light in the 3D + * scene. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Generates a string representation of this {@link PointLight}. + * + * @return A string describing the current state of this point light. + */ + @Override + public String toString() { + return "PointLight [color=" + color + ", position=" + position + + ", intensity=" + intensity + ", range=" + range + "]"; + } + +} \ No newline at end of file diff --git a/src/main/java/scene/light/SpotLight.java b/src/main/java/scene/light/SpotLight.java new file mode 100644 index 00000000..c6d17e16 --- /dev/null +++ b/src/main/java/scene/light/SpotLight.java @@ -0,0 +1,268 @@ +package scene.light; + +import math.Color; +import math.Vector3f; + +/** + * Represents a spotlight in a 3D scene. + * + * A spotlight emits light in a cone shape, with a defined position, direction, + * cone angle, and concentration (center bias). This class models the essential + * properties of a spotlight, allowing users to specify its behavior and + * appearance in a 3D environment. + * + *
+ * Key properties include:
+ * - Position: The 3D coordinates where the spotlight is located.
+ * - Direction: The orientation direction the spotlight points towards.
+ * - Color: The color of the emitted light.
+ * - Angle: The cone angle, defining the spread of the light in radians.
+ * - Concentration: The exponent controlling how focused the spotlight
+ *   is on its center.
+ * 
+ * + * This class supports both a default spotlight configuration and customizable + * initialization via its constructors. Input values are validated to ensure + * realistic and meaningful spotlight behavior. + */ +public class SpotLight implements Light { + + /** + * 45° in radians, the default cone angle for a standard spotlight. + */ + private static final float DEFAULT_ANGLE = (float) Math.PI / 4; + + /** + * Default center bias value for the spotlight's cone. + */ + private static final float DEFAULT_CONCENTRATION = 10.0f; + + /** + * The default position of the spotlight, located at the origin. + */ + private static final Vector3f DEFAULT_POSITION = new Vector3f(0, 0, 0); + + /** + * The default direction for the spotlight, pointing along the negative + * Z-axis. + */ + private static final Vector3f DEFAULT_DIRECTION = new Vector3f(0, 0, -1); + + /** + * The default color of the spotlight's emitted light (white light). + */ + private static final Color DEFAULT_COLOR = Color.WHITE; + + /** The angle of the spotlight's cone in radians. */ + private float angle; + + /** Determines the spotlight's intensity concentration toward its center. */ + private float concentration; + + /** The position of the spotlight in 3D space. */ + private Vector3f position; + + /** The direction vector indicating the spotlight's orientation. */ + private Vector3f direction; + + /** The color of the emitted spotlight's light. */ + private Color color; + + /** + * Default constructor initializes the spotlight with pre-defined defaults. + * + *
+	 * The defaults include:
+	 * - Position at (0,0,0).
+	 * - Direction pointing along the negative Z-axis.
+	 * - White color.
+	 * - A cone angle of 45° (π/4 radians).
+	 * - A concentration value of 10.0 (focused light)
+	 * 
+ */ + public SpotLight() { + this(DEFAULT_POSITION, DEFAULT_DIRECTION, DEFAULT_COLOR, + DEFAULT_CONCENTRATION, DEFAULT_ANGLE); + } + + /** + * Constructs a new SpotLight instance with the specified properties. + * + *

+ * Initializes the spotlight with the provided position, direction, color, + * concentration, and cone angle values. Each input is validated to ensure it + * adheres to acceptable ranges or requirements. + *

+ * + * @param position The 3D position of the spotlight. Must not be null. + * @param direction The direction the spotlight points towards. Must not + * be null. + * @param color The emitted light's color. Must not be null. + * @param concentration The center bias (intensity focus) of the spotlight + * cone. Must be non-negative. + * @param angle The cone angle in radians. Must be greater than 0 and + * less than or equal to π radians. + * @throws IllegalArgumentException if any of the following conditions are + * met: - `position` is null. - `direction` + * is null. - `color` is null. - + * `concentration` is negative. - `angle` is + * less than or equal to 0, or greater than π + * radians. + */ + public SpotLight(Vector3f position, Vector3f direction, Color color, + float concentration, float angle) { + setPosition(position); + setDirection(direction); + setColor(color); + setConcentration(concentration); + setAngle(angle); + } + + /** + * Gets the angle of the spotlight cone. + * + * @return The cone's angle in radians. + */ + public float getAngle() { + return angle; + } + + /** + * Sets the cone angle, ensuring it is within valid physical limits. + * + * @param angle The new angle of the spotlight cone. + * @throws IllegalArgumentException if the value is less than or equal to 0 or + * exceeds π radians. + */ + public void setAngle(float angle) { + if (angle <= 0 || angle > Math.PI) { + throw new IllegalArgumentException( + "Angle must be between 0 and PI radians."); + } + this.angle = angle; + } + + /** + * Gets the concentration (center bias) of the spotlight's cone. + * + * @return The concentration value of the spotlight. + */ + public float getConcentration() { + return concentration; + } + + /** + * Sets the concentration value for the spotlight cone's focus. + * + * @param concentration The new concentration value. + * @throws IllegalArgumentException if the value is negative. + */ + public void setConcentration(float concentration) { + if (concentration < 0) { + throw new IllegalArgumentException("Concentration must be non-negative."); + } + this.concentration = concentration; + } + + /** + * Retrieves the direction vector of the spotlight. + * + * @return The current direction vector. + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction vector of the spotlight. + * + * @param direction The new direction vector. + * @throws IllegalArgumentException if the provided vector is null. + */ + public void setDirection(Vector3f direction) { + if (direction == null) { + throw new IllegalArgumentException("Direction cannot be null."); + } + this.direction = direction; + } + + /** + * Retrieves the position of the spotlight. + * + * @return The position vector. + */ + public Vector3f getPosition() { + return position; + } + + /** + * Sets the position of the spotlight in 3D space. + * + * @param position The new position vector. + * @throws IllegalArgumentException if the provided vector is null. + */ + public void setPosition(Vector3f position) { + if (position == null) { + throw new IllegalArgumentException("Position cannot be null."); + } + this.position = position; + } + + /** + * Retrieves the color of the spotlight's light. + * + * @return The spotlight's color. + */ + @Override + public Color getColor() { + return color; + } + + /** + * Sets the color of the spotlight's emitted light. + * + * @param color The new color value. + * @throws IllegalArgumentException if the provided color is null. + */ + public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } + this.color = color; + } + + /** + * Determines the type of light, specifically `LightType.SPOT`. + * + * @return The type of light. + */ + @Override + public LightType getType() { + return LightType.SPOT; + } + + /** + * Renders the spotlight using the provided rendering system. + * + * Delegates rendering logic to the specified {@link LightRenderer}. + * + * @param renderer The renderer responsible for spotlight rendering. + */ + @Override + public void render(LightRenderer renderer) { + renderer.render(this); + } + + /** + * Provides a string representation of this spotlight instance for debugging. + * + * @return String describing the current state of the spotlight. + */ + @Override + public String toString() { + return "SpotLight [angle=" + angle + ", concentration=" + concentration + + ", position=" + position + ", direction=" + direction + ", color=" + + color + "]"; + } + +} \ No newline at end of file From ff70217260aae5bbea5abca626f56cba64c4911d Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 14:23:49 +0100 Subject: [PATCH 12/15] Refactoring. Changes Slider name and implemented renderer. --- src/main/java/workspace/ui/Slider.java | 117 ----------------- src/main/java/workspace/ui/UiSlider.java | 120 ++++++++++++++++++ .../workspace/ui/renderer/SliderRenderer.java | 47 +++++++ 3 files changed, 167 insertions(+), 117 deletions(-) delete mode 100644 src/main/java/workspace/ui/Slider.java create mode 100644 src/main/java/workspace/ui/UiSlider.java create mode 100644 src/main/java/workspace/ui/renderer/SliderRenderer.java diff --git a/src/main/java/workspace/ui/Slider.java b/src/main/java/workspace/ui/Slider.java deleted file mode 100644 index 5a6f17f1..00000000 --- a/src/main/java/workspace/ui/Slider.java +++ /dev/null @@ -1,117 +0,0 @@ -package workspace.ui; - -import math.Mathf; -import workspace.laf.UiValues; -import workspace.ui.border.Insets; -import workspace.ui.event.ISliderCallBack; - -public class Slider extends UiComponent { - - private float value; - - private float minValue = 0; - - private float maxValue = 3; - - private float posX; - - private ISliderCallBack sliderCallBack; - - private String text = "Slider"; - - @Override - public void renderSelf(Graphics g) { - Insets insets = getInsets(); - - g.setColor(getBackground()); - g.fillRect(insets.getLeft(), insets.getRight(), - width - insets.getHorizontalInsets(), - height - insets.getVerticalInsets()); - - g.setColor(getForeground()); - g.fillRect(getWidth() + posX, 0, 5, getHeight()); - - g.setColor(UiValues.SLIDER_LIGHT); - g.fillRect(0, 0, getWidth() + posX, getHeight()); - - g.setColor(foreground); - g.text(" " + value, 4, g.getTextSize() + g.textDescent()); - g.text(text, getWidth() + 10, g.getTextSize() + g.textDescent()); - } - - public void onMouseDragged(int x, int y) { - posX = x - getWidth() - getX(); - clampPosX(); // Ensure posX stays within bounds - updateValue(); - if (sliderCallBack != null) - sliderCallBack.valueChanged(value); - } - - @Override - public void onMouseClicked(int x, int y) { - // Optionally, jump posX to the clicked position - } - - private void updateValue() { - // Map posX to the slider's value range -// value = Mathf.map(posX, 0, getWidth(), minValue, maxValue); -// value = Mathf.clamp(value, minValue, maxValue); // Ensure value stays in range - value = maxValue + Mathf.map(posX, 0, getWidth(), minValue, maxValue); - } - - private void updatePosX() { -// posX = Mathf.map(value, minValue, maxValue, x, x + width); - - // Map value back to posX -// posX = Mathf.map(value, minValue, maxValue, x, x + getWidth()); -// clampPosX(); // Ensure posX stays within bounds - } - - private void clampPosX() { -// // Ensure posX doesn't exceed slider's drawable area -// posX = Mathf.clamp(posX, 0, getWidth()); - } - - public float getValue() { - return value; - } - - public void setValue(float value) { -// this.value = Mathf.clamp(value, minValue, maxValue); // Clamp the value - updatePosX(); // Update posX to reflect the new value - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public ISliderCallBack getSliderCallBack() { - return sliderCallBack; - } - - public void setSliderCallBack(ISliderCallBack sliderCallBack) { - this.sliderCallBack = sliderCallBack; - } - - public float getMinValue() { - return minValue; - } - - public void setMinValue(float minValue) { - this.minValue = minValue; - updatePosX(); // Update posX to reflect any change in range - } - - public float getMaxValue() { - return maxValue; - } - - public void setMaxValue(float maxValue) { - this.maxValue = maxValue; - updatePosX(); // Update posX to reflect any change in range - } -} \ No newline at end of file diff --git a/src/main/java/workspace/ui/UiSlider.java b/src/main/java/workspace/ui/UiSlider.java new file mode 100644 index 00000000..14209ee2 --- /dev/null +++ b/src/main/java/workspace/ui/UiSlider.java @@ -0,0 +1,120 @@ +package workspace.ui; + +import math.Mathf; +import workspace.ui.border.Insets; +import workspace.ui.event.ISliderCallBack; +import workspace.ui.renderer.SliderRenderer; + +/** + * A customizable UI slider component for selecting a value within a defined + * range. Supports dragging and value change callbacks. + */ +public class UiSlider extends UiComponent { + + private float value; + + private float minValue = 0; + + private float maxValue = 1; + + private float handlePosition; + + private String text = "Slider"; + + private ISliderCallBack callback; + + public UiSlider() { + setRenderer(new SliderRenderer()); + } + + @Override + public void onMouseDragged(int mouseX, int mouseY) { + super.onMouseDragged(mouseX, mouseY); + updateHandlePosition(mouseX); + updateValue(); + if (callback != null) { + callback.valueChanged(value); + } + } + + @Override + public void onMouseClicked(int mouseX, int mouseY) { + super.onMouseClicked(mouseX, mouseY); + updateHandlePosition(mouseX); + updateValue(); + if (callback != null) { + callback.valueChanged(value); + } + } + + private void updateHandlePosition(int mouseX) { + Insets insets = getInsets(); + int trackStart = getX() + insets.getLeft(); // Account for global x position + int trackWidth = getWidth() - insets.getHorizontalInsets(); + + // Adjust handlePosition relative to the track's start position + handlePosition = Mathf.clamp(mouseX - trackStart, 0, trackWidth); + } + + private void updateValue() { + Insets insets = getInsets(); + int trackWidth = getWidth() - insets.getHorizontalInsets(); + + value = Mathf.map(handlePosition, 0, trackWidth, minValue, maxValue); + } + + public float getValue() { + return value; + } + + public void setValue(float value) { + this.value = Mathf.clamp(value, minValue, maxValue); + updateHandlePositionFromValue(); + } + + private void updateHandlePositionFromValue() { + Insets insets = getInsets(); + int trackWidth = getWidth() - insets.getHorizontalInsets(); + +// handlePosition = Mathf.map(value, minValue, maxValue, 0, trackWidth); + } + + public float getMinValue() { + return minValue; + } + + public void setMinValue(float minValue) { + this.minValue = minValue; + updateHandlePositionFromValue(); + } + + public float getMaxValue() { + return maxValue; + } + + public void setMaxValue(float maxValue) { + this.maxValue = maxValue; + updateHandlePositionFromValue(); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public ISliderCallBack getSliderCallback() { + return callback; + } + + public void setSliderCallBack(ISliderCallBack callback) { + this.callback = callback; + } + + public float getHandlePosition() { + return handlePosition; + } + +} diff --git a/src/main/java/workspace/ui/renderer/SliderRenderer.java b/src/main/java/workspace/ui/renderer/SliderRenderer.java new file mode 100644 index 00000000..b753a04f --- /dev/null +++ b/src/main/java/workspace/ui/renderer/SliderRenderer.java @@ -0,0 +1,47 @@ +package workspace.ui.renderer; + +import workspace.ui.Graphics; +import workspace.ui.UiSlider; +import workspace.ui.border.Insets; +import workspace.ui.elements.UiElement; + +/** + * Renderer for the slider component. + */ +public class SliderRenderer implements Renderer { + + @Override + public void render(Graphics g, UiElement element) { + UiSlider slider = (UiSlider) element; + Insets insets = slider.getInsets(); + + int trackStartX = insets.getLeft(); + int trackStartY = insets.getTop(); + int trackWidth = slider.getWidth() - insets.getHorizontalInsets(); + int trackHeight = slider.getHeight() - insets.getVerticalInsets(); + + // Draw track + g.setColor(slider.getBackground()); + g.fillRect(trackStartX, trackStartY, trackWidth, trackHeight); + + // Draw filled portion +// g.setColor(slider.getForeground()); + g.setColor(60, 60, 60); + g.fillRect(trackStartX, trackStartY, (int) slider.getHandlePosition(), + trackHeight); + + // Draw handle + g.setColor(slider.getForeground()); + int handleWidth = 5; + int handleX = (int) (trackStartX + slider.getHandlePosition() + - handleWidth / 2); + g.fillRect(handleX, trackStartY, handleWidth, trackHeight); + + // Draw label and value + g.setColor(slider.getForeground()); + g.text(slider.getText() + ":" + slider.getValue(), + insets.getLeft() + slider.getWidth() + 10, + g.getTextSize() + g.textDescent()); + } + +} \ No newline at end of file From 0f940c0318371669eaf844eb5c2184196b409d52 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 14:24:33 +0100 Subject: [PATCH 13/15] Moved to the according package. --- src/main/java/workspace/ui/{ => elements}/UiSlider.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/workspace/ui/{ => elements}/UiSlider.java (100%) diff --git a/src/main/java/workspace/ui/UiSlider.java b/src/main/java/workspace/ui/elements/UiSlider.java similarity index 100% rename from src/main/java/workspace/ui/UiSlider.java rename to src/main/java/workspace/ui/elements/UiSlider.java From f23738192756cc1fe8e371793946cbacbf79a179 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 14:24:57 +0100 Subject: [PATCH 14/15] Moved to the according package. --- src/main/java/workspace/ui/elements/UiSlider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/workspace/ui/elements/UiSlider.java b/src/main/java/workspace/ui/elements/UiSlider.java index 14209ee2..b6f92b3e 100644 --- a/src/main/java/workspace/ui/elements/UiSlider.java +++ b/src/main/java/workspace/ui/elements/UiSlider.java @@ -1,6 +1,7 @@ -package workspace.ui; +package workspace.ui.elements; import math.Mathf; +import workspace.ui.UiComponent; import workspace.ui.border.Insets; import workspace.ui.event.ISliderCallBack; import workspace.ui.renderer.SliderRenderer; From 0b571428983fed83cac1e8b6bac7408223d7b55f Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Sat, 14 Dec 2024 14:25:16 +0100 Subject: [PATCH 15/15] Moved to the according package. --- src/main/java/workspace/ui/renderer/SliderRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/workspace/ui/renderer/SliderRenderer.java b/src/main/java/workspace/ui/renderer/SliderRenderer.java index b753a04f..91d37070 100644 --- a/src/main/java/workspace/ui/renderer/SliderRenderer.java +++ b/src/main/java/workspace/ui/renderer/SliderRenderer.java @@ -1,9 +1,9 @@ package workspace.ui.renderer; import workspace.ui.Graphics; -import workspace.ui.UiSlider; import workspace.ui.border.Insets; import workspace.ui.elements.UiElement; +import workspace.ui.elements.UiSlider; /** * Renderer for the slider component.