diff options
author | Superkat32 <89557012+Superkat32@users.noreply.github.com> | 2024-04-11 19:34:45 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-12 00:34:45 +0100 |
commit | 3f607dbea3c4c7a0ef30dd4709f6d5469d869c19 (patch) | |
tree | 99427adbbbf5feb3ed9b7b4580539bf652fddb11 /src/main/java/dev/isxander/yacl3/gui/controllers | |
parent | 25ba978582957647e9a8b3069823df43928f32eb (diff) | |
download | YetAnotherConfigLib-3f607dbea3c4c7a0ef30dd4709f6d5469d869c19.tar.gz YetAnotherConfigLib-3f607dbea3c4c7a0ef30dd4709f6d5469d869c19.tar.bz2 YetAnotherConfigLib-3f607dbea3c4c7a0ef30dd4709f6d5469d869c19.zip |
Color Picker For Color Controllers (#140)
* Initial work on color picker - gradient rendering
FEAT: Color picker rendering progress. The selected color, HSL, and RGB gradients all render
FEAT: Added new method for rendering a rainbow in the AbstractWidget class
FEAT: Added a new method for rendering a sideways gradient in the AbstractWidget class
TEST: Added mouse-only detection for enabling/disabling the color picker. It needs some work to allow for controller support(e.g. a button instead of the current mouseX/mouseY detection)
TEST: Started work on the RGB slider detection. Needs a lot of work, and probably needs to be moved as well
TEST: Added an extra color option in the "aaaaaaaaaaaa..." category
BUG: The color picker has some "z-fighting" issues with options behind it
BUG: The color picker needs to be in its own widget, not the way it is right now
Everything is still heavy WIP, I just needed a checkpoint/backup for my work thus far
* Official color picker element
REFACTOR: Moved almost all of the color picker code/rendering outside of the ColorControllerElement and into a new ColorPickerElement
REFACTOR: Renamed a few variables
* Slider x calculation
FEAT: Added a method which determines the slider's x pos based upon the pending value's hue
BUG: If the color is completely black or white, the slider automatically goes to the beginning, as if the color was completely red
BUG: The color picker "z-fighting" with the option behind it is still an issue
* Some "z-fighting" fixing
FIX: Fixed majority of the z-fighting with the color picker
BUG: The option behind the color picker still gets selected. For example, the string controller behind the color picker can be typed in after clicking the color picker
* Sound effect changes
FEAT: Made the down button sound only play when clicking the color preview, which enables the color picker
FIX: Fixed a bug where the down button sound would play upon clicking the color option(doesn't occur with other string related controllers)
* Back to work! - Some refactors I don't remember the point of
REFACTOR: I seemed to have refactored a few things. I did this a month ago, and don't remember anything. It probably doesn't matter, because I have plans for a huge refactor anyways in the near futre
* Beginning of the end (Huge improvements, technical challenge overcome)
FEAT: Began work on the hue slider's hue choosing mechanisms. It took slightly longer than I feel like it should've to figure this out, but I'm happy with its current implementation, and the ideas I have to improve it.
REFACTOR: Huge refactor on the color picker and how it is rendered. Firstly, the values are now almost not-hardcoded at all, being dynamic to the screen's gui size.
REFACTOR: Moved the whole color picker to its own class
REFACTOR: Removed the test color variable in the "aaaaaaaaa..." category
BUG: Color picker's x/xLimit seems to be flipped incorrectly
BUG: HUGE lag spike while choosing the value's hue(I'm unsure how I'm going to fix this)
TODO: Possible better implementation of the color picker as a whole to be clicked on not using the temporary workaround
This is more of a checkpoint for me than anything. I've spent multiple hours each night working on this, and losing this progress would be terrible.
* Bug fixes, some gradient refactoring, small commented out code cleanup
FIX: Apparently fixed the huge lag spike when changing the hue... sometimes. It seems to have something to do with hotswapping, which shouldn't affect anybody outside of dev. env.(will do more testing later)
FIX: Fixed the hue slider gradient being incorrectly displayed
REFACTOR: Updated the param. usage in gradient related methods. For some reason, I had them sorta pretty janky when being used. While the code for the actual methods seem more confusing, using said methods is now less confusing. Will probably come back to this later
REFACTOR: Deleted small bits of commented out code
* Mouse click checkpoint
WIP: Some code for the color picker clicking stuff. Hopefully to be improved soon, but this prototype actually fully works(just implemented in a way I'm not currently happy with)
* Mouse click checkpoint 2
REFACTOR: Cleaned up some of the prototype mouse click code
BUG: Dragging the color picker doesn't work
I'm saving this incase I want to revert later. I'm very confident there is a better way than this, however. Which is why I'm going to continue testing new ideas until I find something better.
* Color picker widget correct mouse clicking
FEAT: Made it so that the color picker element from a color controller gets added to the YACL Screen's children, which handles rendering and mouse click events
FIX: Finally, at last, fixed the color picker mouse clicking z-fighting
BUG: Changing the color from the color picker, then typing in the color controller crashes the game
This took many hours. I spent a long time trying to fix the color picker being removed crashing the game. I thought there were 5 different reasons for as to why it was crashing before finally finding out the true reason. A huge cleanup and possibly making the ColorPickerElement extend the AbstractWidget class instead of the StringElement class might be better.
* Mouse clicking cleanup
REFACTOR: Removed commented out code which previously handled the mouse clicking for the color picker
BUG: Attempting to scroll through the YACL screen while hovering over the color picker ceases all scrolling
* Even more cleanup
REFACTOR: Cleaned up the ColorPickerElement class, making it only have methods and variables it needs/will need
REFACTOR: Optmizied imports for ColorController class and updated some FIXMEs
REFACTOR: Removed old, unused test method in YACLScreen class
* Color picker - clicking does cool things now
FEAT: Made it so that clicking on the hue slider of the color picker will now result with the controller's hue changing
REFACTOR: Made it so that the hue slider thumb now properly moves only when it should
REFACTOR: Clicking on the saturation/light gradient no longer changes the hue(will have proper support for sat/light gradient soon)
* Initial work on color picker redesign
REFACTOR: Updated the background for the color picker, now looking like an inventory container background
TODO: I'm most likely going to touch up on most of the main rendering code, as it is a mess
* Color picker visual redesign
REFACTOR: Completely redesigned the color picker's looks, having an inventory container like background now.
REFACTOR: I think I increased the hue slider thumb's height by 2 pixels to adjust for the new redesign to stay satisfying
FIX: Fixed the colorPickerDim.y and colorPickerDim.yLimit being flipped(that was easier and harder than I thought it would be both at the same time)
FIX: Cleaned up a bunch of messy rendering code. Turns out, it worked, but wasn't as easy to change as I thought. Should be somewhat better now, I might come back and clean it up some more though
* Saturation/Light Picking + Bug fixes + Better mini preview outline
FEAT: Made it so that you can now choose the saturation and light values of a color! Holding down the mouse button allows you to leave the dedicated box for easier color picking. Doing the same with the hue slider doesn't affect the saturation/light picking either
REFACTOR: If a color is very bright, the mini color preview outline will change to a light grey upon hovering instead of plain white. This is to indicate to new users who may have a very bright color as an option that it can still be clicked on.
FIX: Fixed a bug where choosing a color that was too dark would result in the saturation being reset
FIX: Fixed a bug where having too dark of a color would mess up the hue slider
TEST: Tried making the background of the color picker a texture. I was unsuccessful this time, but I'm going to try again soon.
* Typing bug fix and temporary float color picker fix
FIX: Fixed a bug where typing in the color controller while the color picker is visible would result in the color picker desyncing
TEMPFIX: Added a temporary workaround to the color picker floating when the color controller wasn't visible anymore. A better fix will be added in the future. The current workaround probably isn't great for performance.
* Color picker texture background
FEAT: Color picker background texture
FEAT: Transparent square texture
REFACTOR: Made the background of the color picker use a texture instead of manual rendering
REFACTOR: Removed code for manual rendering of the color picker background, as it has been replaced
REFACTOR: Removed unused code from the YACLScreen which I didn't mean to commit in the first place
CHORE: Optimized imports for ColorController
* Some comments I guess?
FEAT: Added some comments a couple weeks ago.
I'm going to look into the popupscreen now.
* Popup Color Picker Screen
FEAT: Started work on testing the Color Picker as a popup screen
Oh, oh my! I've managed to solve more problems in 3 hours than I was able to in 3 weeks with the popup screen-like function!
* General improvements to the Popup Color Picker Screen
FIX: Fixed the color picker not always appearing on first click after closing
FIX: Fixed the color picker scrolling when it shouldn't be
FIX: Fixed the color picker's color controller's color preview's outline(goodness) not highlighting while the color picker was visible
* Color Picker Test category, beginning of alpha in color picker, move color picker if there isn't room above the controller, and another scrolling bug fix
TEST: Added a new Color Picker test category
FEAT: Made it so that if there isn't enough room for a color picker to be easily usable above a color controller, the color picker will appear beneath the color controller
FEAT: Started work on alpha-related stuff for the color picker
FIX: Fixed a bug where there were about 2 pixels worth of area where scrolling would result in the color picker "desyncing" from the color controller
* Finished Alpha slider
FEAT: Finished the alpha slider if the color picker's color controller has it enabled
FEAT: Added some extra color options to the test config to showcase other features of the color picker
* Cleanup and bug fixes
FIX: Fixed a bug where the "fillSidewaysGradient" method was just completely broken and made zero sense.
REFACTOR: Improved(hopefully) the "drawRainbowGradient" method's code
REFACTOR: Cleaned up some unused methods in the ColorPickerElement
REFACTOR: Changed the transparent texture's sizing to 7x7, so that is doesn't clip vertically (still clips a little bit horizontally)
REFACTOR: Removed some commented out code in some other classes from previous testing
* More cleanup
REFACTOR: Did some additional cleanup in the ColorController.java class to remove really old code that was added before hte ColorPickerElement class was moved outside the ColorController class
* Optimize imports
REFACTOR: Optimize imports
Forgot I told IntelliJ not to do that automatically, oops
* Color Picker Changes 1
FIX: Fixed a bug where you could type while the color picker was active without first clicking anywhere
REFACTOR: Removed unused accessWideners
REFACTOR: Changed the Color Picker Popup title to a translatable string
REFACTOR: Cleanup in ColorController.java, including using the protected screen variable and using the Dimension#isPointInside method
REFACTOR: Cleanup in ColorPickerElement.java including:
- Remove full paths for ResourceLocations
- Not capturing the YACLScreen
- Using "control.allowAlpha" instead of storing the boolean
- No longer saving the ColorController
- All variables related to the color picker's dimensions are now private final
- Changed the order of the rendered items to more closely respect their z-level
- Names of some methods
* Extracted PopupColorPickerScreen.java to PopupControllerScreen
REFACTOR: Generalized all popup related stuff from color picker to controller
FIX: Actually fixed keyboard/typed character actions on the color picker without first clicking anywhere
* Color preview outline fading
FEAT: Added color preview outline fading to the color controller. The color preview outline will slowly flash from white to black indicating to a user to press it to enable the color picker. It has a boolean ready to be changed to a config option for when that gets added.
* Popup Controller Future Proofing 1
FIX: Made changes based on feedback to ensure that all popup controller widget related items can easily be used for the dropdown controller, and any future controllers that may need it.
* Color picker y is now controlled by the color controller
REFACTOR: Made it so that the color picker y is now set by the color controller
REFACTOR: The color picker now gets removed if the color controller is partially offscreen
REFACTOR: Removed all code related to the manual scrolling of the color picker/popup widget
* Fix option widget list being given
FIX: Removed the option widget list parameter for the PopupControllerScreen, because it isn't used anymore
* ColorPickerWidget
REFACTOR: Renamed ColorPickerElement to ColorPickerWidget
FIX: Small changes when YaclScreen#clearPopupControllerWidget is called.
* Attempt 2 at fixing weird clearing popup controller loop
FIX: (Hopefully) fixed a bug where the YaclScreen#clearPopupController method would get called twice, instead of just once.
* Color picker indicator automatically disables upon discovery
FEAT: Added a boolean to the YACLConfig which determines if a color controller's color preview's outline(color picker indicator) should flash or not. This boolean is on by default, and automatically disables itself upon the color picker's first opening.
* Moving color picker beneath controller better detection implementation + config fix
REFACTOR: Improved the color picker's implementation detecting if it should be beneath the controller or not(moved order of operations)
FIX: Fixed the config option for flashing the color picker indicator not being a serial entry
* Add multi-version support
---------
Co-authored-by: isXander <xander@isxander.dev>
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/gui/controllers')
4 files changed, 698 insertions, 3 deletions
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java index 56e6d30..3c0a5fc 100644 --- a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java @@ -8,6 +8,7 @@ import dev.isxander.yacl3.gui.AbstractWidget; import dev.isxander.yacl3.gui.YACLScreen; import dev.isxander.yacl3.gui.controllers.string.IStringController; import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import dev.isxander.yacl3.platform.YACLConfig; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; @@ -101,10 +102,13 @@ public class ColorController implements IStringController<Color> { public static class ColorControllerElement extends StringControllerElement { private final ColorController colorController; + private ColorPickerWidget colorPickerWidget; protected MutableDimension<Integer> colorPreviewDim; - private final List<Character> allowedChars; + public boolean hoveredOverColorPreview = false; + private boolean colorPickerVisible = false; + private int previewOutlineFadeTicks = 0; public ColorControllerElement(ColorController control, YACLScreen screen, Dimension<Integer> dim) { super(control, screen, dim, true); @@ -114,13 +118,18 @@ public class ColorController implements IStringController<Color> { @Override protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + hovered = isMouseOver(mouseX, mouseY); + if (isHovered()) { - colorPreviewDim.move(-inputFieldBounds.width() - 5, 0); + colorPreviewDim.move(-inputFieldBounds.width() - 8, -2); + colorPreviewDim.expand(4, 4); + previewOutlineFadeTicks++; super.drawValueText(graphics, mouseX, mouseY, delta); } graphics.fill(colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), colorController.option().pendingValue().getRGB()); - drawOutline(graphics, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, 0xFF000000); + Color outlineColor = getPreviewOutlineColor(hoveredOverColorPreview || isMouseOverColorPreview(mouseX, mouseY)); + drawOutline(graphics, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, outlineColor.getRGB()); } @Override @@ -193,6 +202,14 @@ public class ColorController implements IStringController<Color> { int previewSize = (dim.height() - getYPadding() * 2) / 2; colorPreviewDim = Dimension.ofInt(dim.xLimit() - getXPadding() - previewSize, dim.centerY() - previewSize / 2, previewSize, previewSize); + + if(colorPickerWidget != null) { + colorPickerWidget.setDimension(colorPickerWidget.getDimension().withY(this.getDimension().y())); + //checks if the color controller is being partially rendered offscreen + if(this.getDimension().y() < screen.tabArea.top() || this.getDimension().yLimit() > screen.tabArea.bottom()) { + removeColorPicker(); + } + } } @Override @@ -210,11 +227,121 @@ public class ColorController implements IStringController<Color> { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (super.mouseClicked(mouseX, mouseY, button)) { + //Detects if the user has clicked the color preview + if(isMouseOverColorPreview(mouseX, mouseY)) { + playDownSound(); + createOrRemoveColorPicker(); + if(YACLConfig.HANDLER.instance().showColorPickerIndicator) { + YACLConfig.HANDLER.instance().showColorPickerIndicator = false; + YACLConfig.HANDLER.save(); + } + } caretPos = Math.max(1, caretPos); setSelectionLength(); return true; } + return false; } + + public boolean isMouseOverColorPreview(double mouseX, double mouseY) { + return colorPreviewDim.isPointInside((int) mouseX, (int) mouseY); + } + + public void createOrRemoveColorPicker() { + colorPickerVisible = !colorPickerVisible; + if(colorPickerVisible) { + colorPickerWidget = createColorPicker(); + screen.addPopupControllerWidget(colorPickerWidget); + } else { + removeColorPicker(); + } + } + + @Override + public void unfocus() { + if(colorPickerVisible) { + removeColorPicker(); + } + previewOutlineFadeTicks = 0; + super.unfocus(); + } + + public Color getPreviewOutlineColor(boolean colorPreviewHovered) { + Color outlineColor = new Color(0xFF000000); + Color highlightedColor = getHighlightedOutlineColor(); + + if(!hovered && !colorPreviewHovered) { + previewOutlineFadeTicks = 0; + return outlineColor; + } + + int fadeInTicks = 80; + int fadeOutTicks = fadeInTicks + 120; + + if(colorPreviewHovered) { + //white/light grey if the color preview is being hovered + previewOutlineFadeTicks = 0; + return highlightedColor; + } else if(YACLConfig.HANDLER.instance().showColorPickerIndicator) { + if(previewOutlineFadeTicks <= fadeInTicks) { + //fade to white + return getFadedColor(outlineColor, highlightedColor, previewOutlineFadeTicks, fadeInTicks); + } else if (previewOutlineFadeTicks <= fadeOutTicks) { + //fade to black + return getFadedColor(highlightedColor, outlineColor, previewOutlineFadeTicks - fadeInTicks, fadeOutTicks - fadeInTicks); + } + + if(previewOutlineFadeTicks >= fadeInTicks + fadeOutTicks + 10) { + //reset fade + previewOutlineFadeTicks = 0; + } + } + + return outlineColor; + } + + private Color getFadedColor(Color original, Color fadeToColor, int fadeTick, int maxFadeTicks) { + int red = fadeToColor.getRed() - original.getRed(); + int green = fadeToColor.getGreen() - original.getGreen(); + int blue = fadeToColor.getBlue() - original.getBlue(); + return new Color( + original.getRed() + ((red * fadeTick) / maxFadeTicks), + original.getGreen() + ((green * fadeTick) / maxFadeTicks), + original.getBlue() + ((blue * fadeTick) / maxFadeTicks) + ); + } + + private Color getHighlightedOutlineColor() { + //Brightness detector in case a developer has their starting color bright + //Makes the outline indicating to a user that the mini color preview can be clicked a light grey rather than white + //For reference, there is about a 10 digit moving room in saturation and light + Color pendingValue = colorController.option().pendingValue(); + float[] HSL = Color.RGBtoHSB(pendingValue.getRed(), pendingValue.getGreen(), pendingValue.getBlue(), null); + Color highlightedColor = new Color(0xFFFFFFFF); + if(HSL[1] < 0.1f && HSL[2] > 0.9f) { + highlightedColor = new Color(0xFFC6C6C6); + } + return highlightedColor; + } + + public ColorPickerWidget colorPickerWidget() { + return colorPickerWidget; + } + + public boolean colorPickerVisible() { + return colorPickerVisible; + } + + public ColorPickerWidget createColorPicker() { + return new ColorPickerWidget(colorController, screen, getDimension(), this); + } + + public void removeColorPicker() { + screen.clearPopupControllerWidget(); + this.colorPickerVisible = false; + this.colorPickerWidget = null; + this.hoveredOverColorPreview = false; //set to false in favor of the manual checking here to be done + } } } diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java new file mode 100644 index 0000000..c7664dc --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/ColorPickerWidget.java @@ -0,0 +1,459 @@ +package dev.isxander.yacl3.gui.controllers; + +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.api.utils.MutableDimension; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.utils.YACLRenderHelper; +import dev.isxander.yacl3.platform.YACLPlatform; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; + +import java.awt.*; + +public class ColorPickerWidget extends ControllerPopupWidget<ColorController> { + /*? if >1.20.1 {*/ + private static final ResourceLocation COLOR_PICKER_LOCATION = YACLPlatform.rl("controller/colorpicker"); + private static final ResourceLocation TRANSPARENT_TEXTURE_LOCATION = YACLPlatform.rl("controller/transparent"); + /*? } else {*//* + // nineslice and repeating only work on a 256x atlas + private static final ResourceLocation COLOR_PICKER_ATLAS = YACLPlatform.rl("textures/gui/colorpicker-atlas.png"); + *//*?}*/ + + private final ColorController controller; + private final ColorController.ColorControllerElement entryWidget; + protected MutableDimension<Integer> colorPickerDim; + protected MutableDimension<Integer> previewColorDim; + protected MutableDimension<Integer> saturationLightDim; + protected MutableDimension<Integer> hueGradientDim; + protected MutableDimension<Integer> alphaGradientDim; + private boolean mouseDown; + private boolean hueSliderDown; + private boolean satLightGradientDown; + private boolean alphaSliderDown; + private int hueThumbX; + private int satLightThumbX; + private int alphaThumbX; + private boolean charTyped; + + //The width of the outline between each color picker element(color preview, saturation/light gradient, hue gradient) + //Note: Additional space may need to be manually made upon increasing the outline + private final int outline = 1; + + //The main color preview's portion of the color picker as a whole + //example: if previewPortion is set to 7, then the color preview will take up + //a 7th of the color picker's width + private final int previewPortion = 7; + + //The height in pixels of the hue slider + //example: if the sliderHeight is set to 7, then the hue slider will be 7 pixels, with some extra padding between + //the color preview and the HSL gradient to allow for an outline(determined by the "outline" int) + private final int sliderHeight = 7; + + //The x padding between the color preview and saturation/light gradient. + //Does NOT account for the outline on its own + private final int paddingX = 1; + + //The y padding between the hue gradient and color preview & saturation/light gradient. + //Does NOT account for the outline on its own + private final int paddingY = 3; + + + private float[] HSL; + private float hue; + private float saturation; + private float light; + private int alpha; + + public ColorPickerWidget(ColorController control, YACLScreen screen, Dimension<Integer> dim, ColorController.ColorControllerElement entryWidget) { + super(control, screen, dim, entryWidget); + this.controller = control; + this.entryWidget = entryWidget; + + setDimension(dim); + + updateHSL(); + setThumbX(); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + updateHSL(); + + int thumbWidth = 4; + int thumbHeight = 4; + + graphics.pose().pushPose(); + graphics.pose().translate(0, 0, 10); // render over text + + //Background + /*? if >1.20.3 { */ + graphics.blitSprite(COLOR_PICKER_LOCATION, colorPickerDim.x() - 5, colorPickerDim.y() - 5, 1, colorPickerDim.width() + 10, colorPickerDim.height() + 10); + /*? } else {*//* + graphics.blitNineSliced(COLOR_PICKER_ATLAS, colorPickerDim.x() - 5, colorPickerDim.y() - 5, colorPickerDim.width() + 10, colorPickerDim.height() + 10, 3, 236, 34, 0, 0); + *//*?}*/ + + //Main color preview + //outline + graphics.fill(previewColorDim.x() - outline, previewColorDim.y() - outline, previewColorDim.xLimit() + outline, previewColorDim.yLimit() + outline, Color.black.getRGB()); + //transparent texture - must be rendered BEFORE the main color preview + if(controller.allowAlpha()) { + /*? if >1.20.3 { */ + graphics.blitSprite(TRANSPARENT_TEXTURE_LOCATION, previewColorDim.x(), previewColorDim.y(), 3, previewColorDim.width(), previewColorDim.height()); + /*? } else {*//* + graphics.blitRepeating(COLOR_PICKER_ATLAS, previewColorDim.x(), previewColorDim.y(), previewColorDim.width(), previewColorDim.height(), 236, 0, 8, 8); + *//*?}*/ + } + //main color preview + graphics.fill(previewColorDim.x(), previewColorDim.y(), previewColorDim.xLimit(), previewColorDim.yLimit(), controller.option().pendingValue().getRGB()); + + //Saturation/light gradient + //outline + graphics.fill(saturationLightDim.x() - outline, saturationLightDim.y() - outline, saturationLightDim.xLimit() + outline, saturationLightDim.yLimit() + outline, Color.black.getRGB()); + //White to pending color's RGB from hue, left to right + fillSidewaysGradient(graphics, saturationLightDim.x(), saturationLightDim.y(), saturationLightDim.xLimit(), saturationLightDim.yLimit(), 0xFFFFFFFF, (int) getRgbFromHueX()); + //Transparent to black, top to bottom + graphics.fillGradient(saturationLightDim.x(), saturationLightDim.y(), saturationLightDim.xLimit(), saturationLightDim.yLimit(), 0x00000000, 0xFF000000); + //Sat/light thumb shadow + graphics.fill(satLightThumbX - thumbWidth / 2 - 2, getSatLightThumbY() + thumbHeight / 2 + 2, satLightThumbX + thumbWidth / 2 + 1, getSatLightThumbY() - thumbHeight / 2 - 1, 0xFF404040); + //Sat/light thumb - extra 1 pixel on left and top to make it centered + graphics.fill(satLightThumbX - thumbWidth / 2 - 1, getSatLightThumbY() + thumbHeight / 2 + 1, satLightThumbX + thumbWidth / 2, getSatLightThumbY() - thumbHeight / 2, -1); + + //Hue gradient + //outline + graphics.fill(hueGradientDim.x() - outline, hueGradientDim.y() - outline, hueGradientDim.xLimit() + outline, hueGradientDim.yLimit() + outline, Color.black.getRGB()); + //Hue rainbow gradient + drawRainbowGradient(graphics, hueGradientDim.x(), hueGradientDim.y(), hueGradientDim.xLimit(), hueGradientDim.yLimit()); + //Hue slider thumb shadow + graphics.fill(hueThumbX - thumbWidth / 2 - 1, hueGradientDim.y() - outline - 1, hueThumbX + thumbWidth / 2 + 1, hueGradientDim.yLimit() + outline + 1, 0xFF404040); + //Hue slider thumb + graphics.fill(hueThumbX - thumbWidth / 2, hueGradientDim.y() - outline, hueThumbX + thumbWidth / 2, hueGradientDim.yLimit() + outline, -1); + + if(controller.allowAlpha()) { + //outline + graphics.fill(alphaGradientDim.x() - outline, alphaGradientDim.y() - outline, alphaGradientDim.xLimit() + outline, alphaGradientDim.yLimit() + outline, Color.black.getRGB()); + //Transparent texture + /*? if >1.20.3 { */ + graphics.blitSprite(TRANSPARENT_TEXTURE_LOCATION, alphaGradientDim.x(), alphaGradientDim.y(), 3, alphaGradientDim.width(), sliderHeight); + /*? } else {*//* + graphics.blitRepeating(COLOR_PICKER_ATLAS, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.width(), sliderHeight, 236, 0, 8, 8); + *//*?}*/ + //Pending color to transparent + fillSidewaysGradient(graphics, alphaGradientDim.x(), alphaGradientDim.y(), alphaGradientDim.xLimit(), alphaGradientDim.yLimit(), getRgbWithoutAlpha(), 0x00000000); + //Alpha slider thumb shadow + graphics.fill(alphaThumbX - thumbWidth / 2 - 1, alphaGradientDim.y() - outline - 1, alphaThumbX + thumbWidth / 2 + 1, alphaGradientDim.yLimit() + outline + 1, 0xFF404040); + //Alpha slider thumb + graphics.fill(alphaThumbX - thumbWidth / 2, alphaGradientDim.y() - outline, alphaThumbX + thumbWidth / 2, alphaGradientDim.yLimit() + outline, -1); + } + + //graphics.blitRepeating(COLOR_PICKER_ATLAS, colorPickerDim.x(), colorPickerDim.y(), colorPickerDim.width(), colorPickerDim.height(), 237, 0, 4, 4); + + graphics.pose().popPose(); + } + + public boolean clickedHueSlider(double mouseX, double mouseY) { + if (satLightGradientDown || alphaSliderDown) return false; + + if (mouseY >= hueGradientDim.y() && mouseY <= hueGradientDim.yLimit()) { + if (mouseX >= hueGradientDim.x() && mouseX <= hueGradientDim.xLimit()) { + hueSliderDown = true; + } + } + + if (hueSliderDown) { + hueThumbX = (int) Mth.clamp(mouseX, hueGradientDim.x(), hueGradientDim.xLimit()); + } + + return hueSliderDown; + } + + public boolean clickedSatLightGradient(double mouseX, double mouseY) { + if (hueSliderDown || alphaSliderDown) return false; + + if (mouseX >= saturationLightDim.x() && mouseX <= saturationLightDim.xLimit()) { + if (mouseY >= saturationLightDim.y() && mouseY <= saturationLightDim.yLimit()) { + satLightGradientDown = true; + } + } + + if(satLightGradientDown) { + satLightThumbX = (int) Mth.clamp(mouseX, saturationLightDim.x(), saturationLightDim.xLimit()); + } + + return satLightGradientDown; + } + + public boolean clickedAlphaSlider(double mouseX, double mouseY) { + if (satLightGradientDown || hueSliderDown) return false; + + if (mouseX >= alphaGradientDim.x() && mouseX <= alphaGradientDim.xLimit()) { + if (mouseY >= alphaGradientDim.y() && mouseY <= alphaGradientDim.yLimit()) { + alphaSliderDown = true; + } + } + + if (alphaSliderDown) { + alphaThumbX = (int) Mth.clamp(mouseX, alphaGradientDim.x(), alphaGradientDim.xLimit()); + } + + return alphaSliderDown; + } + + public void setColorFromMouseClick(double mouseX, double mouseY) { + if (clickedSatLightGradient(mouseX, mouseY)) { + setSatLightFromMouse(mouseX, mouseY); + } else if (clickedHueSlider(mouseX, mouseY)) { + setHueFromMouse(mouseX); + } else if (controller.allowAlpha() && clickedAlphaSlider(mouseX, mouseY)) { + setAlphaFromMouse(mouseX); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isMouseOver(mouseX, mouseY)) { + mouseDown = true; + hueSliderDown = false; + satLightGradientDown = false; + alphaSliderDown = false; + setColorFromMouseClick(mouseX, mouseY); + return true; + } else if (entryWidget.isMouseOver(mouseX, mouseY)) { + return entryWidget.mouseClicked(mouseX, mouseY, button); + } else { + close(); //removes color picker + return false; + } + } + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + mouseDown = false; + return false; + } + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + //Checks if the mouse is either over the color picker or the color controller + //The addition/subtraction of the outline and extra 3 pixels is to account for both the outline and the background + if (mouseX >= colorPickerDim.x() - outline - 3 && mouseX <= colorPickerDim.xLimit() + outline + 3 + && mouseY >= colorPickerDim.y() - outline - 3 && mouseY <= colorPickerDim.yLimit() + outline + 3) { + return true; + } + return false; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (mouseDown || isMouseOver(mouseX, mouseY)) { + setColorFromMouseClick(mouseX, mouseY); + return true; + } + return entryWidget.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + //Done to allow for typing whilst the color picker is visible + charTyped = true; + return entryWidget.charTyped(chr, modifiers); + } + + @Override + public void setDimension(Dimension<Integer> dim) { + super.setDimension(dim); + + int colorPickerHeight = (dim.height() * 2) + 7; + int colorPickerX = dim.centerX() - getXPadding() * 2; + int colorPickerY = dim.y() - colorPickerHeight - sliderHeight; + int alphaSliderHeight = 0; + if (controller.allowAlpha()) { + alphaSliderHeight = sliderHeight + outline + paddingY; + colorPickerHeight += alphaSliderHeight; + colorPickerY -= alphaSliderHeight; + } + + //Check if the color picker should be moved to beneath the controller + //Add additional numbers after colorPickerY to reduce the "strictness" of this detection + if (colorPickerY < screen.tabArea.top()) { + colorPickerY = dim.yLimit() + sliderHeight; + } + + //A single dimension for the entire color picker as a whole + //Division is used for the main color preview, saturation/light picker, and hue slider to determine their dimensions + colorPickerDim = Dimension.ofInt(colorPickerX, colorPickerY, dim.xLimit() - colorPickerX, colorPickerHeight); + + previewColorDim = Dimension.ofInt(colorPickerDim.x(), colorPickerDim.y(), (colorPickerDim.x() + (colorPickerDim.xLimit() / previewPortion) - paddingX) - colorPickerDim.x(), (colorPickerDim.yLimit() - sliderHeight - paddingY) - colorPickerDim.y() - alphaSliderHeight); + saturationLightDim = Dimension.ofInt(colorPickerDim.x() + (colorPickerDim.xLimit() / previewPortion) + paddingX + 1, colorPickerDim.y(), colorPickerDim.xLimit() - (colorPickerDim.x() + (colorPickerDim.xLimit() / previewPortion) + paddingX + 1), (colorPickerDim.yLimit() - sliderHeight - paddingY) - colorPickerDim.y() - alphaSliderHeight); + hueGradientDim = Dimension.ofInt(colorPickerDim.x(), colorPickerDim.yLimit() - sliderHeight - alphaSliderHeight, colorPickerDim.width(), sliderHeight); + if (controller.allowAlpha()) { + alphaGradientDim = Dimension.ofInt(hueGradientDim.x(), hueGradientDim.y() + alphaSliderHeight, hueGradientDim.width(), sliderHeight); + } + } + + @Override + public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + entryWidget.hoveredOverColorPreview = entryWidget.isMouseOverColorPreview(mouseX, mouseY); + } + + @Override + public void close() { + entryWidget.removeColorPicker(); + } + + @Override + public Component popupTitle() { + return Component.translatable("yacl.control.color.color_picker_title"); + } + + public void setThumbX() { + //Sets the thumb x for both hue and sat/light + hueThumbX = getHueThumbX(); + satLightThumbX = getSatLightThumbX(); + if (controller.allowAlpha()) { + alphaThumbX = getAlphaThumbX(); + } + } + + protected int getHueThumbX() { + int min = hueGradientDim.x(); + int max = hueGradientDim.xLimit(); + int value = (int) (min + hueGradientDim.width() * this.hue); + + return Mth.clamp(value, min, max); + } + + protected int getSatLightThumbX() { + int min = saturationLightDim.x(); + int max = saturationLightDim.xLimit(); + int value = (int) (min + (saturationLightDim.width() * this.saturation)); + + return Mth.clamp(value, min, max); + } + + protected int getSatLightThumbY() { + int min = saturationLightDim.y(); + int max = saturationLightDim.yLimit(); + int value = (int) (min + (saturationLightDim.height() * (1.0f - this.light))); + + return Mth.clamp(value, min, max); + } + + protected int getAlphaThumbX() { + int min = alphaGradientDim.x(); + int max = alphaGradientDim.xLimit(); + int value = max - (alphaGradientDim.width() * this.alpha / 255); + + return Mth.clamp(value, min, max); + } + + public void setHueFromMouse(double mouseX) { + //Changes the hue of the pending color based on the mouseX's pos. + //relative to the colorPickerDim's x/xLimit + if(mouseX < hueGradientDim.x()) { + this.hue = 0f; + } else if (mouseX > hueGradientDim.xLimit()) { + this.hue = 1f; + } else { + float newHue = (float) (mouseX - hueGradientDim.x()) / hueGradientDim.width(); + + this.hue = Mth.clamp(newHue, 0f, 1f); + } + + setColorControllerFromHSL(); + } + + public void setSatLightFromMouse(double mouseX, double mouseY) { + if(mouseX < saturationLightDim.x()) { + this.saturation = 0f; + } else if (mouseX > saturationLightDim.xLimit()) { + this.saturation = 1f; + } else { + float newSat = (float) (mouseX - saturationLightDim.x()) / saturationLightDim.width(); + + this.saturation = Mth.clamp(newSat, 0f, 1.0f); + } + + if(mouseY < saturationLightDim.y()) { + this.light = 1f; + } else if (mouseY > saturationLightDim.yLimit()) { + this.light = 0f; + } else { + float newLight = (float) (mouseY - saturationLightDim.y()) / saturationLightDim.height(); + + this.light = Mth.clamp(1f - newLight, 0f, 1.0f); + } + + setColorControllerFromHSL(); + } + + public void setAlphaFromMouse(double mouseX) { + //Changes the alpha of the pending color based on the mouseX's pos. + if(mouseX < alphaGradientDim.x()) { + this.alpha = 255; + } else if (mouseX > alphaGradientDim.xLimit()) { + this.alpha = 0; + } else { + int newAlpha = (int) ((mouseX - alphaGradientDim.xLimit()) / alphaGradientDim.width() * -255); + + this.alpha = Mth.clamp(newAlpha, 0, 255); + } + + setColorControllerFromHSL(); + } + + public void setColorControllerFromHSL() { + //Updates the current color controller's pending value based from HSL to RGB + float trueHue = (float) (hueThumbX - colorPickerDim.x()) / colorPickerDim.width(); + Color color = Color.getHSBColor(trueHue, saturation, light); + Color returnColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha); + controller.option().requestSet(returnColor); + } + + protected void updateHSL() { + this.HSL = getHSL(); + this.hue = hue(); + this.saturation = saturation(); + this.light = light(); + this.alpha = getAlpha(); + if(charTyped) { + setThumbX(); + charTyped = false; + } + } + + protected float[] getHSL() { + Color pendingValue = controller.option().pendingValue(); + return Color.RGBtoHSB(pendingValue.getRed(), pendingValue.getGreen(), pendingValue.getBlue(), null); + } + + protected float hue() { + //Gets the hue of the pending value + return HSL[0]; + } + + protected float saturation() { + //Gets the saturation of the pending value + return HSL[1]; + } + + protected float light() { + //Gets the light/brightness/value(has a few different names, all refer to the same thing) of the pending value + return HSL[2]; + } + + protected int getAlpha() { + return controller.option().pendingValue().getAlpha(); + } + + protected float getRgbFromHueX() { + float trueHue = (float) (hueThumbX - colorPickerDim.x()) / colorPickerDim.width(); + + return Color.HSBtoRGB(trueHue, 1, 1); + } + + protected int getRgbWithoutAlpha() { + Color pendingColor = controller.option().pendingValue(); + Color returnColor = new Color(pendingColor.getRed(), pendingColor.getGreen(), pendingColor.getBlue(), 255); + return returnColor.getRGB(); + } +} diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerPopupWidget.java b/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerPopupWidget.java new file mode 100644 index 0000000..e2f19bc --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerPopupWidget.java @@ -0,0 +1,39 @@ +package dev.isxander.yacl3.gui.controllers; + +import dev.isxander.yacl3.api.Controller; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.Component; + +public abstract class ControllerPopupWidget<T extends Controller<?>> extends ControllerWidget<Controller<?>> implements GuiEventListener { + public final ControllerWidget<?> entryWidget; + public ControllerPopupWidget(T control, YACLScreen screen, Dimension<Integer> dim, ControllerWidget<?> entryWidget) { + super(control, screen, dim); + this.entryWidget = entryWidget; + } + + public ControllerWidget<?> entryWidget() { + return entryWidget; + } + + public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {} + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + return entryWidget.keyPressed(keyCode, scanCode, modifiers); + } + + public void close() {} + + public Component popupTitle() { + return Component.translatable("yacl.control.text.blank"); + } + + @Override + protected int getHoveredControlWidth() { + return 0; + } + +} diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java b/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java new file mode 100644 index 0000000..f6a5db3 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java @@ -0,0 +1,70 @@ +package dev.isxander.yacl3.gui.controllers; + +import dev.isxander.yacl3.gui.YACLScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; + +public class PopupControllerScreen extends Screen { + private final YACLScreen backgroundYaclScreen; + private final ControllerPopupWidget<?> controllerPopup; + public PopupControllerScreen(YACLScreen backgroundYaclScreen, ControllerPopupWidget<?> controllerPopup) { + super(controllerPopup.popupTitle()); //Gets narrated by the narrator + this.backgroundYaclScreen = backgroundYaclScreen; + this.controllerPopup = controllerPopup; + } + + + @Override + protected void init() { + this.addRenderableWidget(this.controllerPopup); + } + + @Override + public void resize(Minecraft minecraft, int width, int height) { + minecraft.setScreen(backgroundYaclScreen); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + controllerPopup.renderBackground(graphics, mouseX, mouseY, delta); + this.backgroundYaclScreen.render(graphics, -1, -1, delta); //mouseX/Y set to -1 to prevent hovering outlines + + super.render(graphics, mouseX, mouseY, delta); + } + + @Override + public void renderBackground( + GuiGraphics guiGraphics + /*? if >1.20.1 {*/, + int mouseX, + int mouseY, + float partialTick + /*?}*/ + ) { + + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, /*? if >1.20.1 {*/ double scrollX, /*?}*/ double scrollY) { + backgroundYaclScreen.mouseScrolled(mouseX, mouseY, /*? if >1.20.1 {*/ scrollX, /*?}*/ scrollY); //mouseX & mouseY are needed here + return super.mouseScrolled(mouseX, mouseY, /*? if >1.20.1 {*/ scrollX, /*?}*/ scrollY); + } + + @Override + public boolean charTyped(char codePoint, int modifiers) { + return controllerPopup.charTyped(codePoint, modifiers); + } + + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + return controllerPopup.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public void onClose() { + this.minecraft.screen = backgroundYaclScreen; + } + +} |