Implemented ImageLabel widget

This commit is contained in:
OmniaX-Dev 2026-04-20 08:50:38 +02:00
parent 67a2b5f10e
commit 27feac8fb7
15 changed files with 211 additions and 123 deletions

View file

@ -156,7 +156,6 @@ window.backgroundColor = rgba(160, 160, 160, 255)
}
% ====================
(image) {
path = file("img.png")
animated = true
@ -166,14 +165,11 @@ window.backgroundColor = rgba(160, 160, 160, 255)
rows: 4, \
frameWidth: 256, \
frameHeight: 256)
backgroundColor = #999999FF
backgroundGradient = gradientV(rgb(160, 160, 160) - rgb(120, 120, 120))
useBackgroundGradient = true
backgroundGradient = gradientV(color_crimson - $accentColorDark)
showBorder = true
borderWidth = 1
borderColor = $accentColorDark
borderRadius = 0
borderWidth = 1
showBackground = true
showBorder = true
useBackgroundGradient = true
padding = rect(15, 8, 15, 8)
margin = rect(0, 0, 0, 0)
padding = rect(15, 15, 15, 15)
}

View file

@ -25,6 +25,8 @@ Implement cursors in Stylesheet
FIX: Window getting exponentially bigger when Desktop scale changes
FIX: Refreshing scroll when content extent changes
Add Dark Mode Default theme
FIX: Float values in stylesheet needing to have decimal point to be read as float
Make parsing of vec2 and rect in stylesheet, consistent with other value functions like gradients and animations
@ -34,14 +36,14 @@ Implement following widgets:
***Panel / Container
***Title Label
***ScrollView
Button
***Image / Icon
***Button
Image/Icon
Radio Button
Grouping
Text Input
Horizontal Slider
Vertical Slider
Image / Icon
ListBox
ComboBox
TreeView

View file

@ -1 +1 @@
2071
2072

View file

@ -105,6 +105,7 @@ namespace ogfx
inline ostd::Stylesheet::VariableList& getDefaultStylesheetVariableList(void) { return m_defaultStylesheetVariables; }
inline virtual void setTheme(const ostd::Stylesheet& theme) { }
inline BasicRenderer2D& getGFX(void) { return m_gfx; }
inline bool isInitialized(void) const { return m_initialized; }
inline bool isRunning(void) const { return m_running; }
inline bool isVisible(void) const { return m_visible; }
@ -145,6 +146,7 @@ namespace ogfx
protected:
SDL_Window* m_window { nullptr };
SDL_Renderer* m_renderer { nullptr };
BasicRenderer2D m_gfx;
ostd::ConsoleOutputHandler m_out;
GraphicsWindowOutputHandler m_wout;
const ostd::Stylesheet* m_guiTheme { nullptr };
@ -263,7 +265,6 @@ namespace ogfx
void __on_fixed_update(void) override;
protected:
BasicRenderer2D m_gfx;
widgets::RootWidget m_rootWidget { *this };
ostd::StepTimer m_fixedUpdateTimer;
ostd::StepTimer::TimePoint m_lastFrameTime;

View file

@ -20,6 +20,8 @@
#include "Label.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../../../ostd/io/FileSystem.hpp"
#include "../Window.hpp"
namespace ogfx
{
@ -62,6 +64,62 @@ namespace ogfx
setSize({ cast<f32>(size.x), cast<f32>(size.y) });
m_textChanged = false;
}
ImageLabel& ImageLabel::create(const String& filePath)
{
setImage(filePath);
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::widgets::ImageLabel");
disableChildren();
disableBackground();
disableBorder();
setStylesheetCategoryName("image");
setSize(64, 64);
validate();
return *this;
}
void ImageLabel::applyTheme(const ostd::Stylesheet& theme)
{
enableAnimated(getThemeValue<bool>(theme, "animated", m_animated));
setAnimationData(getThemeValue<AnimationData>(theme, "animation", m_animData));
String filePath = getThemeValue<String>(theme, "path", m_image.getFilePath());
if (filePath != m_image.getFilePath())
setImage(filePath);
setSize(getThemeValue<Vec2>(theme, "size", getSize()));
if (isAnimatedEnabled())
{
m_anim.create(m_animData);
m_anim.setSpriteSheet(m_image);
}
}
void ImageLabel::onDraw(ogfx::BasicRenderer2D& gfx)
{
if (isAnimatedEnabled())
gfx.drawAnimation(m_anim, getGlobalPosition() + getPadding().topLeft(), getSize() - getPadding().bottomRight());
else
gfx.drawImage(m_image, getGlobalPosition() + getPadding().topLeft(), getSize() - getPadding().bottomRight());
}
void ImageLabel::onUpdate(void)
{
if (isAnimatedEnabled())
m_anim.update();
}
void ImageLabel::setImage(const String& filePath)
{
if (filePath == "") return;
if (!ostd::FileSystem::fileExists(filePath))
return;
m_image.destroy();
m_image.loadFromFile(filePath, getWindow().getGFX());
}
}
}
}

View file

@ -21,6 +21,8 @@
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/resources/Image.hpp>
#include <ogfx/utils/Animation.hpp>
namespace ogfx
{
@ -45,6 +47,26 @@ namespace ogfx
String m_text { "" };
bool m_textChanged { false };
};
class ImageLabel : public Widget
{
public:
inline ImageLabel(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); }
inline ImageLabel(WindowCore& window, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); }
ImageLabel& create(const String& filePath);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onUpdate(void) override;
void setImage(const String& filePath);
inline Image& getImage(void) { return m_image; }
OSTD_BOOL_PARAM_GETSET_E(Animated, m_animated);
OSTD_PARAM_GETSET(AnimationData, AnimationData, m_animData);
private:
Image m_image;
Animation m_anim;
AnimationData m_animData;
bool m_animated { false };
};
}
}
}

View file

@ -323,7 +323,7 @@ namespace ogfx
{
if (isVScrollEnabled())
{
bool mouseInsideHScrollbar = m_hScrollbar.isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y }) && needsHScroll();
bool mouseInsideHScrollbar = m_hScrollbar.isMouseInside() && needsHScroll();
if (std::abs(event.mouse->scrollAmount.y) > 0 && !mouseInsideHScrollbar)
{
m_scrollVelocity.y += m_scrollSpeed * event.mouse->scrollAmount.y * m_scrollSpeedMultiplier;

View file

@ -244,7 +244,7 @@ namespace ogfx
if (!isVisible())
return;
beforeDraw(gfx);
if (m_useBackgroundGradient && m_showBorder)
if (m_useBackgroundGradient)
gfx.fillGradientRect({ getGlobalPosition(), getSize() }, m_backgroundGradient);
else if (m_showBackground)
gfx.fillRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderRadius);

View file

@ -218,7 +218,8 @@ namespace ogfx
return set_error_state(tErrors::NoFont);
if (m_font == nullptr)
return set_error_state(tErrors::NullFont);
if (fontSize == m_fontSize || fontSize <= 0) return set_error_state(tErrors::NoError);
if (fontSize == m_fontSize || fontSize <= 0)
return set_error_state(tErrors::NoError);
TTF_SetFontSize(m_font, fontSize);
m_fontSize = fontSize;
return set_error_state(tErrors::NoError);

View file

@ -27,15 +27,20 @@ namespace ogfx
{
void Image::destroy(void)
{
if (isValid() && isLoaded())
SDL_DestroyTexture(m_sdl_texture);
m_loaded = false;
m_sdl_texture = nullptr;
m_width = 0;
m_height = 0;
m_filePath = "";
invalidate();
}
Image& Image::loadFromFile(const String& filePath, BasicRenderer2D& gfx)
{
if (isLoaded() && isValid())
return *this;
if (!gfx.isInitialized())
return *this; //TODO: Error
m_sdl_texture = IMG_LoadTexture(gfx.getWindow().getSDLRenderer(), filePath.c_str());
@ -45,6 +50,7 @@ namespace ogfx
return *this;
}
SDL_GetTextureSize(m_sdl_texture, &m_width, &m_height);
m_filePath = filePath;
m_loaded = true;
setTypeName("ogfx::Image");
validate();

View file

@ -38,9 +38,11 @@ namespace ogfx
inline Vec2 getSize(void) const { return { m_width, m_height }; }
inline bool isLoaded(void) const { return m_loaded; }
inline SDL_Texture* getSDLTexture(void) const { return m_sdl_texture; }
inline String getFilePath(void) const { return m_filePath; }
private:
SDL_Texture* m_sdl_texture { nullptr };
String m_filePath { "" };
f32 m_width { 0 };
f32 m_height { 0 };
bool m_loaded { false };

View file

@ -253,7 +253,7 @@ namespace ostd
return false;
String key = line.new_substr(0, line.indexOf("=")).trim();
String valuePreserveCase = line.new_substr(line.indexOf("=") + 1).trim();
String value = line.new_substr(line.indexOf("=") + 1).trim().toLower();
String value = valuePreserveCase.new_toLower();
if (key == "")
return false;
String themeID = "";
@ -286,7 +286,7 @@ namespace ostd
};
auto l_isFile = [this](String& value) -> bool {
value.trim();
if (value.startsWith("file(") && value.endsWith(")"))
if (value.new_toLower().startsWith("file(") && value.endsWith(")"))
{
value.substr(5, value.len() - 1).trim();
if (value.startsWith("\"") && value.endsWith("\""))
@ -299,7 +299,7 @@ namespace ostd
};
auto l_isAnim = [this](String& value) -> bool {
value.trim();
if (value.startsWith("anim(") && value.endsWith(")"))
if (value.new_toLower().startsWith("anim(") && value.endsWith(")"))
{
value.substr(5, value.len() - 1).trim();
return true;
@ -331,25 +331,25 @@ namespace ostd
}
else if (l_isColorGradientValue(value))
{
ColorGradient grad = parseColorGradient(value);
ColorGradient grad = parseColorGradient(valuePreserveCase, variables);
if (grad.isInvalid())
return false;
set(key, grad, themeID);
}
else if (l_isAnim(value))
else if (l_isAnim(valuePreserveCase))
{
bool outError = false;
AnimationData ad = parseAnim(value, outError);
AnimationData ad = parseAnim(valuePreserveCase, outError, variables);
if (outError)
return false;
set(key, ad, themeID);
}
else if (l_isFile(value))
else if (l_isFile(valuePreserveCase))
{
bool exists = ostd::FileSystem::fileExists(value) ||
ostd::FileSystem::fileExists("./" + value);
bool exists = ostd::FileSystem::fileExists(valuePreserveCase) ||
ostd::FileSystem::fileExists("./" + valuePreserveCase);
if (exists)
set(key, value, themeID);
set(key, valuePreserveCase, themeID);
else
return false;
}
@ -392,20 +392,7 @@ namespace ostd
{
if (exitCondition)
return false;
String lineCopy = line;
std::vector<std::pair<String, std::pair<String, bool>>> sortedVars(variables.begin(), variables.end());
std::sort(sortedVars.begin(), sortedVars.end(), [](const auto& a, const auto& b) {
return a.first.len() > b.first.len();
});
for (const auto&[var, val] : sortedVars)
{
if (lineCopy.contains(var))
{
lineCopy.replaceAll(var, val.first);
break;
}
}
return parseThemeFileLine(lineCopy, variables, true);
return parseThemeFileLine(replaceVariables(line, variables), variables, true);
}
return true;
}
@ -477,13 +464,13 @@ namespace ostd
return newLines;
}
ColorGradient Stylesheet::parseColorGradient(const String& _value)
ColorGradient Stylesheet::parseColorGradient(const String& _value, const VariableList& variables)
{
String value = _value.new_toLower().trim();
String value = _value.new_trim();
ColorGradient grad;
f32 angle = 0.0f;
bool weighted = false;
String gradientFunc = value.new_substr(0, value.indexOf("(")).trim();
String gradientFunc = value.new_substr(0, value.indexOf("(")).trim().toLower();
String gradientVal = value.substr(value.indexOf("(") + 1, value.lastIndexOf(")")).trim();
if (gradientVal == "" || !gradientVal.contains("-"))
@ -511,6 +498,9 @@ namespace ostd
i32 index = 0;
for (auto& token : tokens)
{
token = replaceVariables(token, variables);
if (token.new_toLower().startsWith("color(") && token.new_toLower().endsWith(")"))
token.substr(6, token.len() - 1).trim();
grad.addColor(Color(token));
if (index > 0)
grad.addWeight(1.0f);
@ -525,10 +515,14 @@ namespace ostd
{
if (is_color)
{
token = replaceVariables(token, variables);
if (token.new_toLower().startsWith("color(") && token.new_toLower().endsWith(")"))
token.substr(6, token.len() - 1).trim();
grad.addColor(Color(token));
}
else
{
token = replaceVariables(token, variables);
if (!token.isNumeric(true))
return ColorGradient();
grad.addWeight(token.toFloat());
@ -541,9 +535,9 @@ namespace ostd
return grad;
}
AnimationData Stylesheet::parseAnim(const String& _value, bool& outError)
AnimationData Stylesheet::parseAnim(const String& _value, bool& outError, const VariableList& variables)
{
String value = _value.new_toLower().trim();
String value = _value.new_trim();
AnimationData ad;
auto tokens = value.tokenize(",");
auto l_error = [&](bool err) -> AnimationData {
@ -554,8 +548,9 @@ namespace ostd
{
if (tok.count(":") != 1 || tok.startsWith(":") || tok.endsWith(":"))
return l_error(true);
String prop = tok.new_substr(0, tok.indexOf(":")).trim();
String prop = tok.new_substr(0, tok.indexOf(":")).trim().toLower();
String val = tok.new_substr(tok.indexOf(":") + 1).trim();
val = replaceVariables(val, variables);
if (prop == "framecount")
{
if (!val.isInt())
@ -648,6 +643,25 @@ namespace ostd
return l_error(false);
}
String Stylesheet::replaceVariables(const String& line, const VariableList& variables, bool stop_at_first_match)
{
String lineCopy = line;
std::vector<std::pair<String, std::pair<String, bool>>> sortedVars(variables.begin(), variables.end());
std::sort(sortedVars.begin(), sortedVars.end(), [](const auto& a, const auto& b) {
return a.first.len() > b.first.len();
});
for (const auto&[var, val] : sortedVars)
{
if (lineCopy.contains(var))
{
lineCopy.replaceAll(var, val.first);
if (stop_at_first_match)
break;
}
}
return lineCopy;
}
void Stylesheet::debugPrint(void)
{
for (const auto&[key, value] : m_values)

View file

@ -63,8 +63,9 @@ namespace ostd
bool parseThemeFileLine(const String& line, const VariableList& variables, bool exitCondition = false);
String parseGroupSelector(const String& rawSelector) const;
stdvec<String> parseGroup(const String& selector, const stdvec<String>& group);
ColorGradient parseColorGradient(const String& _value);
AnimationData parseAnim(const String& _value, bool& outError);
ColorGradient parseColorGradient(const String& _value, const VariableList& variables);
AnimationData parseAnim(const String& _value, bool& outError, const VariableList& variables);
String replaceVariables(const String& line, const VariableList& variables, bool stop_at_first_match = true);
private:
stdumap<String, TypeVariant> m_values;

View file

@ -369,13 +369,14 @@ namespace ostd
inline virtual Vec2 topRight(void) const { return Vec2(getx() + getw(), gety()); }
inline virtual Vec2 bottomLeft(void) const { return Vec2(getx(), gety() + geth()); }
inline virtual Vec2 bottomRight(void) const { return Vec2(getx() + getw(), gety() + geth()); }
inline virtual Rectangle edgeRect(void) const { return { getx(), gety(), getx() + getw(), gety() + getw() }; }
inline virtual f32 left(void) const { return getx(); }
inline virtual f32 right(void) const { return getw(); }
inline virtual f32 top(void) const { return gety(); }
inline virtual f32 bottom(void) const { return geth(); }
inline String toString(void) const override { return String("{ ").add(x).add(", ").add(y).add(", ").add(w).add(", ").add(h).add(" }"); }
inline virtual String toString(void) const override { return String("{ ").add(x).add(", ").add(y).add(", ").add(w).add(", ").add(h).add(" }"); }
inline virtual bool intersects(Rectangle rect, bool includeBounds = true) const
{

View file

@ -32,18 +32,6 @@ class Window : public ogfx::gui::Window
inline Window(void) { }
inline void onInitialize(void) override
{
m_img.loadFromFile("./img.png", m_gfx);
ogfx::AnimationData ad;
ad.columns = 9;
ad.rows = 4;
ad.frameWidth = 256;
ad.frameHeight = 256;
ad.frameCount = 36;
ad.fps = 60;
m_anim.create(ad);
m_anim.setSpriteSheet(m_img);
m_label1.setText("Show Panel2");
m_label1.setCallback(ogfx::gui::Widget::eCallback::MousePressed, [&](const ogfx::gui::Event& event) -> void {
m_check1.setChecked(!m_check1.isChecked());
@ -100,6 +88,7 @@ class Window : public ogfx::gui::Window
m_panel2.addWidget(m_panel1, { 400, 50 });
m_panel2.addWidget(m_label1, { 0, 500 });
m_panel2.addWidget(m_btn1, { 0, 300 });
m_panel2.addWidget(m_img, { 20, 150 });
addWidget(m_check1, { 30, 30 });
addWidget(m_panel2, { 500, 100 });
@ -120,14 +109,10 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override
{
gfx.drawAnimation(m_anim, { 200, 200 });
// std::cout << (i32)gfx.getDrawCallCount() << "\n";
// gfx.fillRect(m_panel2.getGlobalPureContentBounds(), { 0, 255, 0, 120 });
}
void onFixedUpdate(void) override
{
m_anim.update();
}
private:
@ -141,10 +126,9 @@ class Window : public ogfx::gui::Window
Panel m_panel3 { *this };
CheckBox m_check1 { *this };
Button m_btn1 { *this };
ImageLabel m_img { *this };
ostd::Stylesheet m_theme;
ogfx::Animation m_anim;
ogfx::Image m_img;
};
i32 main(i32 argc, char** argv)