package speiger.src.coreengine.rendering.gui.components; import java.util.function.Consumer; import java.util.function.ObjIntConsumer; import speiger.src.coreengine.math.misc.ColorUtils; import speiger.src.coreengine.math.misc.Facing; import speiger.src.coreengine.math.misc.FacingList; import speiger.src.coreengine.math.value.IValue; import speiger.src.coreengine.math.value.LiniarValue; import speiger.src.coreengine.math.vector.floats.Vec2f; import speiger.src.coreengine.math.vector.ints.Vec2i; import speiger.src.coreengine.rendering.gui.GuiComponent; import speiger.src.coreengine.rendering.gui.base.IButtonComponent; import speiger.src.coreengine.rendering.gui.components.icon.CrossIcon; import speiger.src.coreengine.rendering.gui.components.icon.LineIcon; import speiger.src.coreengine.rendering.gui.helper.Align; import speiger.src.coreengine.rendering.gui.helper.box.IGuiBox; import speiger.src.coreengine.rendering.gui.helper.constrains.ComponentConstrains; import speiger.src.coreengine.rendering.gui.helper.constrains.ParentConstrain; import speiger.src.coreengine.rendering.gui.helper.constrains.PixelConstrain; import speiger.src.coreengine.rendering.utils.Cursor; import speiger.src.coreengine.utils.functions.ConsumerConverter; import speiger.src.coreengine.utils.helpers.InternalThreadPools; public class WindowComponent extends PanelComponent implements IButtonComponent, ObjIntConsumer { public static final Vec2f DEFAULT_MINIMUM_BOUNDS = Vec2f.newVec(75F, 7.5F); public static final int FLAG_MINIMIZED = 1 << 20; public static final int FLAG_RESIZEABLE = 1 << 21; public static final int FLAG_MOVEABLE = 1 << 22; public static final int FLAG_RESIZEABLE_HORIZONTAL = 1 << 23; public static final int FLAG_RESIZEABLE_VERTICAL = 1 << 24; public static final int FLAG_RESIZE_INVERT = 1 << 25; public static final int WINDOW_FLAG_CLOSEABLE = 1; public static final int WINDOW_FLAG_MINIMIZEABLE = 2; public static final int WINDOW_FLAGS = WINDOW_FLAG_CLOSEABLE | WINDOW_FLAG_MINIMIZEABLE; public static final int DEFAULT_FLAGS = WINDOW_FLAGS | FLAG_RESIZEABLE | FLAG_MOVEABLE; public static final int FIXED_SIZE_WINDOW = WINDOW_FLAGS | FLAG_MOVEABLE; public static final int FIXED_SIZE_POPUP = WINDOW_FLAG_CLOSEABLE | FLAG_MOVEABLE; public static final int DYNAMIC_POPUP = FIXED_SIZE_POPUP | FLAG_RESIZEABLE; public static final int UNCLOSEABLE_WINDOW = WINDOW_FLAG_MINIMIZEABLE | FLAG_RESIZEABLE | FLAG_MOVEABLE; public static final int SUB_WINDOW = WINDOW_FLAG_MINIMIZEABLE | FLAG_RESIZEABLE | FLAG_RESIZE_INVERT; final int flags; FacingList facing = null; String name; int color = ColorUtils.WINDOW_DEFAULT_BACKGROUND; Vec2f lastSize = Vec2f.newMutable(); Vec2i lastClick = Vec2i.newMutable(); IValue animation = null; protected final Consumer closeListener = new ConsumerConverter(0, this); protected final Consumer minimizedListener = new ConsumerConverter(2, this); public WindowComponent(float x, float y, float width, float height, int flags, String name) { super(x, y, width, height); this.name = name; this.flags = flags & WINDOW_FLAGS; setFlag(flags &= ~(WINDOW_FLAGS)); setFlag(FLAG_RESIZEABLE_HORIZONTAL | FLAG_RESIZEABLE_VERTICAL); lastSize.set(getBox().getBaseWidth(), getBox().getBaseHeight()); } @Override public void init() { super.init(); LabelComponent label = new LabelComponent(name, ColorUtils.DARK_GRAY); label.getText().setTextScale(0.4F).horizontal(Align.LEFT_TOP).singleLine(true); addChild(label, new ComponentConstrains(null, null, new ParentConstrain(), new PixelConstrain(7.5F))); float offset = 9F; if((flags & WINDOW_FLAG_CLOSEABLE) != 0) { addChild(new IconButtonComponent(0F, 0F, 7.5F, 7.5F, ColorUtils.RED, new CrossIcon(ColorUtils.WHITE).setPadding(2.5F, 2F)).addUserActionListener(new ConsumerConverter<>(0, this)).setZOffset(0.001F), new ComponentConstrains(new PixelConstrain(offset).setInverted(), null, null, null)); offset += 7.5F; } if((flags & WINDOW_FLAG_MINIMIZEABLE) != 0) { addChild(new IconButtonComponent(0F, 0F, 7.5F, 7.5F, ColorUtils.GRAY, new LineIcon(ColorUtils.WHITE, 0.7F, 0.25F)).addUserActionListener(new ConsumerConverter<>(1, this)).setZOffset(0.001F), new ComponentConstrains(new PixelConstrain(offset).setInverted(), null, null, null)); } if(canMoveIntoForground()) { setFlag(FLAG_RENDER_ORDER); } } protected void updateMinizedState(boolean value) { if(setFlag(FLAG_MINIMIZED, value)) { onChanged(false); } } public final WindowComponent setMinimized(boolean value) { if((flags & WINDOW_FLAG_MINIMIZEABLE) != 0 && setFlag(FLAG_MINIMIZED, value)) { if(value) { Vec2f last = InternalThreadPools.VEC2F.get().set(lastSize); bounds(last.getX(), isFlagSet(FLAG_MINIMIZED) ? getMinimizedY() : last.getY()); lastSize.set(last); InternalThreadPools.VEC2F.accept(last.negate()); } else bounds(lastSize.getX(), lastSize.getY()); onChanged(false); } return this; } public final boolean isMinimized() { return isFlagSet(FLAG_MINIMIZED); } public Vec2f getMinimumBounds() { return DEFAULT_MINIMUM_BOUNDS; } public float getMinimizedY() { return 7.5F; } public WindowComponent setColor(int color) { this.color = color; return this; } @Override public void calculateActualBounds(float[] area, boolean start) { if(animation != null) { float scale = getBox().getScale(); area[0] = Math.min(area[0], getBox().getMinX()); area[1] = Math.min(area[1], getBox().getMinY()); area[2] = Math.max(area[2], lastSize.getX() * scale); area[3] = Math.max(area[3], animation.get(getMinimizedY(), lastSize.getY()) * scale); return; } super.calculateActualBounds(area, start); } @Override protected void updateState() { if(animation == null) { if(isFlagSet(FLAG_MINIMIZED)) { lastSize.setX(getBox().getBaseWidth()); return; } lastSize.set(getBox().getBaseWidth(), getBox().getBaseHeight()); } } @Override protected boolean updateSelf(int mouseX, int mouseY, float particalTicks) { if(animation != null) { animation.update(particalTicks); if(animation.isDone()) { if(animation.get() < 0.1F) { updateMinizedState(true); } Vec2f last = InternalThreadPools.VEC2F.get().set(lastSize); bounds(last.getX(), isFlagSet(FLAG_MINIMIZED) ? getMinimizedY() : last.getY()); lastSize.set(last); InternalThreadPools.VEC2F.accept(last.negate()); animation = null; } notifyListeners(LISTENER_ON_CHANGE); } else if(isFlagSet(FLAG_RESIZEABLE) && !isOverChild(mouseX, mouseY) && !getGui().hasComponentInTheWay(getTopComponent(), mouseX, mouseY)) { FacingList list = getBox().isColiding(mouseX, mouseY) ? getBox().getColidingBorder(mouseX, mouseY, 2F) : null; if(list != null) { if(isFlagNotSet(FLAG_RESIZEABLE_HORIZONTAL)) list = list.remove(FacingList.HORIZONTAL); if(isFlagNotSet(FLAG_RESIZEABLE_VERTICAL)) list = list.remove(FacingList.VERTICAL); bindCursor(list.containsAny(FacingList.VERTICAL) && isFlagNotSet(FLAG_MINIMIZED) ? Cursor.CURSOR_VRESIZE : (list.containsAny(FacingList.HORIZONTAL) ? Cursor.CURSOR_HRESIZE : null)); } } if(isPopup() && !hasChildPopups() && !getGui().isComponentInFront(this)) { requestFocus(); } return true; } @Override protected void preRender() { if(animation != null) { float scale = getBox().getScale(); enableScissors(getBox().getMinX(), getBox().getMinY(), lastSize.getX() * scale, animation.get(getMinimizedY(), lastSize.getY()) * scale); } } @Override protected boolean renderSelf(int mouseX, int mouseY, float particalTicks) { getRenderer().drawQuad(getBox(), color); return true; } @Override protected void postRender() { if(animation != null) { disableScissors(); } } @Override public void accept(GuiComponent value, int index) { switch(index) { case 0: getGui().removeComponent(this); break; case 1: if(animation != null || (flags & WINDOW_FLAG_MINIMIZEABLE) == 0) { break; } animation = (isMinimized() ? new LiniarValue(1F, 0F, 1F) : new LiniarValue(1F, 1F, 0F)).setSmooth(); if(isMinimized()) { bounds(lastSize.getX(), lastSize.getY()); } updateMinizedState(false); break; case 2: value.setVisible(!isMinimized()); break; } } @Override public boolean onClick(int button, int mouseX, int mouseY) { if(isOverChild(mouseX, mouseY) || getGui().hasComponentInTheWay(getTopComponent(), mouseX, mouseY)) { return false; } facing = getBox().getColidingBorder(mouseX, mouseY, 2F); lastClick.set(mouseX, mouseY); if(facing != null) { if(isFlagNotSet(FLAG_RESIZEABLE_HORIZONTAL)) facing = facing.remove(FacingList.HORIZONTAL); if(isFlagNotSet(FLAG_RESIZEABLE_VERTICAL)) facing = facing.remove(FacingList.VERTICAL); } return true; } @Override public boolean onDrag(int mouseX, int mouseY) { if(facing != null) { if(facing.isEmpty() && isFlagSet(FLAG_MOVEABLE)) { move(mouseX - lastClick.getX(), mouseY - lastClick.getY()); } else if(!facing.isEmpty() && isFlagSet(FLAG_RESIZEABLE)) { float scale = getBox().getScale(); float xChange = (mouseX - lastClick.getX()) * (facing.containsAny(FacingList.HORIZONTAL) ? 1F : 0F); float yChange = (mouseY - lastClick.getY()) * (facing.containsAny(FacingList.VERTICAL) ? 1F : 0F); if(isFlagSet(FLAG_RESIZE_INVERT)) { xChange *= -1F; yChange *= -1F; } setMassChanging(); if(facing.contains(Facing.NORTH) && isFlagNotSet(FLAG_MINIMIZED)) { resize(0F, -(yChange / scale)); move(0F, yChange); } else if(facing.contains(Facing.SOUTH) && isFlagNotSet(FLAG_MINIMIZED)) { resize(0F, yChange / scale); } if(facing.contains(Facing.WEST)) { resize(-(xChange / scale) - 1F, 0F); move(xChange, 0F); } else if(facing.contains(Facing.EAST)) { resize(xChange / scale, 0F); } ensureMinimumBounds(); finishMassChanging(); if(xChange > 0F || yChange > 0F) { onChanged(true); } } lastClick.set(mouseX, mouseY); return true; } return false; } @Override public void onRelease(int button, int mouseX, int mouseY) { facing = null; } @Override public boolean canMoveIntoForground() { return true; } @Override public void onFocusLost() { facing = null; } protected void ensureMinimumBounds() { IGuiBox box = getBox(); Vec2f bounds = getMinimumBounds(); if(box.getBaseWidth() < bounds.getX()) { box.setWidth(bounds.getX()); onChanged(true); } if(box.getBaseHeight() < bounds.getY()) { box.setHeight(bounds.getY()); onChanged(true); } } }