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 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 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 acf581a1..0679e4be 100644 --- a/src/main/java/workspace/WorkspaceSideBarUi.java +++ b/src/main/java/workspace/WorkspaceSideBarUi.java @@ -3,303 +3,299 @@ 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 { - 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; + } } 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/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/IActionListener.java b/src/main/java/workspace/ui/IActionListener.java deleted file mode 100644 index 7dd1b5c4..00000000 --- a/src/main/java/workspace/ui/IActionListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package workspace.ui; - -public interface IActionListener { - - void onActionPerformed(); - -} diff --git a/src/main/java/workspace/ui/ISliderCallBack.java b/src/main/java/workspace/ui/ISliderCallBack.java deleted file mode 100644 index d7d18d47..00000000 --- a/src/main/java/workspace/ui/ISliderCallBack.java +++ /dev/null @@ -1,7 +0,0 @@ -package workspace.ui; - -public interface ISliderCallBack { - - void valueChanged(float value); - -} diff --git a/src/main/java/workspace/ui/MouseEvent.java b/src/main/java/workspace/ui/MouseEvent.java deleted file mode 100644 index bacbc120..00000000 --- a/src/main/java/workspace/ui/MouseEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -package workspace.ui; - -public class MouseEvent { - - private int mouseX; - - private int mouseY; - - private int previousMouseX; - - private int previousMouseY; - - 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; - } - - public int getMouseY() { - return mouseY; - } - - public int getPreviousMouseX() { - return previousMouseX; - } - - public int getPreviousMouseY() { - return previousMouseY; - } - -} diff --git a/src/main/java/workspace/ui/Slider.java b/src/main/java/workspace/ui/Slider.java deleted file mode 100644 index 3a14574c..00000000 --- a/src/main/java/workspace/ui/Slider.java +++ /dev/null @@ -1,90 +0,0 @@ -package workspace.ui; - -import math.Mathf; -import workspace.laf.UiValues; -import workspace.ui.border.Insets; - -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 onDraw(Graphics g) { - Insets insets = getInsets(); - - g.setColor(getBackground()); - g.fillRect( - insets.left, insets.right, width - insets.getWidth(), - height - insets.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(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); - } - - @Override - public void onMouseClicked(int x, int y) { - - } - - private void updateValue() { - value = maxValue + Mathf.map(posX, 0, getWidth(), minValue, maxValue); - } - - 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; - } - - public float getMaxValue() { - return maxValue; - } - - public void setMaxValue(float maxValue) { - this.maxValue = maxValue; - } - -} diff --git a/src/main/java/workspace/ui/UiButton.java b/src/main/java/workspace/ui/UiButton.java deleted file mode 100644 index dc0e6a3e..00000000 --- a/src/main/java/workspace/ui/UiButton.java +++ /dev/null @@ -1,53 +0,0 @@ -package workspace.ui; - -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; - } - -} diff --git a/src/main/java/workspace/ui/UiCheckBox.java b/src/main/java/workspace/ui/UiCheckBox.java deleted file mode 100644 index 5056c465..00000000 --- a/src/main/java/workspace/ui/UiCheckBox.java +++ /dev/null @@ -1,80 +0,0 @@ -package workspace.ui; - -import workspace.ui.border.Insets; - -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; - } - -} 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/UiEditorMenu.java b/src/main/java/workspace/ui/UiEditorMenu.java deleted file mode 100644 index 71bb65ac..00000000 --- a/src/main/java/workspace/ui/UiEditorMenu.java +++ /dev/null @@ -1,47 +0,0 @@ -package workspace.ui; - -import workspace.laf.UiConstants; -import workspace.laf.UiValues; - -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; - } - -} diff --git a/src/main/java/workspace/ui/UiLabel.java b/src/main/java/workspace/ui/UiLabel.java deleted file mode 100644 index 899d511d..00000000 --- a/src/main/java/workspace/ui/UiLabel.java +++ /dev/null @@ -1,38 +0,0 @@ -package workspace.ui; - -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; - } - -} diff --git a/src/main/java/workspace/ui/UiPanel.java b/src/main/java/workspace/ui/UiPanel.java deleted file mode 100644 index a30f9ad0..00000000 --- a/src/main/java/workspace/ui/UiPanel.java +++ /dev/null @@ -1,17 +0,0 @@ -package workspace.ui; - -import workspace.ui.border.Insets; - -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() - ); - } - -} diff --git a/src/main/java/workspace/ui/UiSimpleList.java b/src/main/java/workspace/ui/UiSimpleList.java index cd6f3c68..a6160eff 100644 --- a/src/main/java/workspace/ui/UiSimpleList.java +++ b/src/main/java/workspace/ui/UiSimpleList.java @@ -4,7 +4,47 @@ import java.util.List; 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 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(); + } + private List elements; public UiSimpleList() { diff --git a/src/main/java/workspace/ui/ViewGizmo.java b/src/main/java/workspace/ui/ViewGizmo.java deleted file mode 100644 index a32d9215..00000000 --- a/src/main/java/workspace/ui/ViewGizmo.java +++ /dev/null @@ -1,108 +0,0 @@ -package workspace.ui; - -import math.Mathf; -import mesh.Mesh3D; -import mesh.creator.primitives.ConeCreator; -import mesh.creator.primitives.CubeCreator; -import mesh.modifier.RotateXModifier; -import mesh.modifier.ScaleModifier; -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; - } - -} diff --git a/src/main/java/workspace/ui/border/Border.java b/src/main/java/workspace/ui/border/Border.java new file mode 100644 index 00000000..7d9309a7 --- /dev/null +++ b/src/main/java/workspace/ui/border/Border.java @@ -0,0 +1,35 @@ +package workspace.ui.border; + +import workspace.ui.Graphics; + +/** + * 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 { + + /** + * 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); + + /** + * 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/IBorder.java b/src/main/java/workspace/ui/border/IBorder.java deleted file mode 100644 index a53a8171..00000000 --- a/src/main/java/workspace/ui/border/IBorder.java +++ /dev/null @@ -1,11 +0,0 @@ -package workspace.ui.border; - -import workspace.ui.Graphics; - -public interface IBorder { - - void drawBorder(Graphics g2d, int x, int y, int width, int height); - - Insets getInsets(); - -} 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/elements/UiButton.java b/src/main/java/workspace/ui/elements/UiButton.java new file mode 100644 index 00000000..77c7b40f --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiButton.java @@ -0,0 +1,108 @@ +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 { + + /** + * 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 new file mode 100644 index 00000000..c4a4c366 --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiCheckBox.java @@ -0,0 +1,141 @@ +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 { + + /** + * 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 new file mode 100644 index 00000000..dc86d8fb --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiEditorMenu.java @@ -0,0 +1,48 @@ +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; // 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/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/elements/UiLabel.java b/src/main/java/workspace/ui/elements/UiLabel.java new file mode 100644 index 00000000..1f56ebdb --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiLabel.java @@ -0,0 +1,49 @@ +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 { + + /** + * 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 new file mode 100644 index 00000000..9fdde2cd --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiPanel.java @@ -0,0 +1,32 @@ +package workspace.ui.elements; + +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 { + + /** + * 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/UiSlider.java b/src/main/java/workspace/ui/elements/UiSlider.java new file mode 100644 index 00000000..b6f92b3e --- /dev/null +++ b/src/main/java/workspace/ui/elements/UiSlider.java @@ -0,0 +1,121 @@ +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; + +/** + * 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/elements/ViewportCompass.java b/src/main/java/workspace/ui/elements/ViewportCompass.java new file mode 100644 index 00000000..7a6f9c50 --- /dev/null +++ b/src/main/java/workspace/ui/elements/ViewportCompass.java @@ -0,0 +1,158 @@ +package workspace.ui.elements; + +import math.Mathf; +import math.Vector3f; +import mesh.Mesh3D; +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; +import workspace.ui.Graphics; +import workspace.ui.UiComponent; + +/** + * 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 size; + + private float height; + + private Vector3f rotation; + + private Mesh3D cube; + + private Mesh3D coneX; + + private Mesh3D coneY; + + private Mesh3D coneZ; + + /** + * Default constructor initializes the compass with default height, size, and + * creates all required meshes for rendering the compass. + */ + public ViewportCompass() { + 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(); + g.translate(x, y); + 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); + renderMesh(g, coneY, UiConstants.KEY_GIZMO_AXIS_Y_COLOR); + renderMesh(g, coneZ, UiConstants.KEY_GIZMO_AXIS_Z_COLOR); + + 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(); + createConeY(); + 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. + * + * @param rotation The desired rotation vector. + */ + public void setRotation(Vector3f rotation) { + this.rotation.setX(rotation.x); + this.rotation.setY(rotation.y); + this.rotation.setZ(rotation.z); + } + +} diff --git a/src/main/java/workspace/ui/event/IActionListener.java b/src/main/java/workspace/ui/event/IActionListener.java new file mode 100644 index 00000000..1bf31956 --- /dev/null +++ b/src/main/java/workspace/ui/event/IActionListener.java @@ -0,0 +1,43 @@ +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 { + + /** + * 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 new file mode 100644 index 00000000..3c417fa8 --- /dev/null +++ b/src/main/java/workspace/ui/event/ISliderCallBack.java @@ -0,0 +1,7 @@ +package workspace.ui.event; + +public interface ISliderCallBack { + + void valueChanged(float value); + +} diff --git a/src/main/java/workspace/ui/event/MouseEvent.java b/src/main/java/workspace/ui/event/MouseEvent.java new file mode 100644 index 00000000..086e781a --- /dev/null +++ b/src/main/java/workspace/ui/event/MouseEvent.java @@ -0,0 +1,87 @@ +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 { + + /** The current X-coordinate of the mouse. */ + private final int mouseX; + + /** The current Y-coordinate of the mouse. */ + private final int mouseY; + + /** The previous X-coordinate of the mouse. */ + private final int previousMouseX; + + /** The previous Y-coordinate of the mouse. */ + private final int 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; + } + + /** + * Gets the current X-coordinate of the mouse. + * + * @return The current X-coordinate. + */ + public int getMouseX() { + return mouseX; + } + + /** + * Gets the current Y-coordinate of the mouse. + * + * @return The current Y-coordinate. + */ + public int getMouseY() { + return mouseY; + } + + /** + * Gets the previous X-coordinate of the mouse. + * + * @return The previous X-coordinate. + */ + public int getPreviousMouseX() { + return previousMouseX; + } + + /** + * Gets the previous Y-coordinate of the mouse. + * + * @return The previous Y-coordinate. + */ + public int getPreviousMouseY() { + return previousMouseY; + } + +} \ No newline at end of file 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 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..91d37070 --- /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.border.Insets; +import workspace.ui.elements.UiElement; +import workspace.ui.elements.UiSlider; + +/** + * 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