font renderer: word wrap is fix, gui: label scrollbar
This commit is contained in:
+1
-1
@@ -948,7 +948,7 @@ void EditorComponent::Load()
|
||||
helpLabel.Create("HelpLabel");
|
||||
helpLabel.SetText(ss);
|
||||
helpLabel.SetVisible(false);
|
||||
helpLabel.SetColor(wi::Color(113, 183, 214, 100), wi::gui::WIDGETSTATE::IDLE);
|
||||
helpLabel.SetColor(wi::Color(113, 183, 214, 100));
|
||||
GetGUI().AddWidget(&helpLabel);
|
||||
}
|
||||
|
||||
|
||||
+103
-129
@@ -98,40 +98,46 @@ namespace wi::font
|
||||
};
|
||||
static wi::vector<FontStyle> fontStyles;
|
||||
|
||||
template<typename T>
|
||||
uint32_t WriteVertices(FontVertex* vertexList, const T* text, Params params)
|
||||
struct Cursor
|
||||
{
|
||||
const FontStyle& fontStyle = fontStyles[params.style];
|
||||
const float fontScale = stbtt_ScaleForPixelHeight(&fontStyle.fontInfo, (float)params.size);
|
||||
|
||||
XMFLOAT2 pos = {};
|
||||
XMFLOAT2 size = {};
|
||||
uint32_t quadCount = 0;
|
||||
float line = 0;
|
||||
float pos = 0;
|
||||
float pos_last_letter = 0;
|
||||
size_t last_word_begin = 0;
|
||||
bool start_new_word = false;
|
||||
};
|
||||
|
||||
static thread_local wi::vector<FontVertex> vertexList;
|
||||
|
||||
template<typename T>
|
||||
Cursor ParseText(const T* text, size_t text_length, Params params)
|
||||
{
|
||||
Cursor cursor;
|
||||
const FontStyle& fontStyle = fontStyles[params.style];
|
||||
const float fontScale = stbtt_ScaleForPixelHeight(&fontStyle.fontInfo, (float)params.size);
|
||||
vertexList.clear();
|
||||
|
||||
auto word_wrap = [&] {
|
||||
start_new_word = true;
|
||||
if (last_word_begin > 0 && params.h_wrap >= 0 && pos >= params.h_wrap - 1)
|
||||
cursor.start_new_word = true;
|
||||
if (cursor.last_word_begin > 0 && params.h_wrap >= 0 && cursor.pos.x >= params.h_wrap - 1)
|
||||
{
|
||||
// Word ended and wrap detected, push down last word by one line:
|
||||
float word_offset = vertexList[last_word_begin].pos.x;
|
||||
for (size_t i = last_word_begin; i < quadCount * 4; ++i)
|
||||
float word_offset = vertexList[cursor.last_word_begin].pos.x;
|
||||
for (size_t i = cursor.last_word_begin; i < cursor.quadCount * 4; ++i)
|
||||
{
|
||||
vertexList[i].pos.x -= word_offset;
|
||||
vertexList[i].pos.y += LINEBREAK_SIZE;
|
||||
}
|
||||
line += LINEBREAK_SIZE;
|
||||
pos -= word_offset;
|
||||
cursor.pos.x -= word_offset;
|
||||
cursor.pos.y += LINEBREAK_SIZE;
|
||||
}
|
||||
};
|
||||
|
||||
cursor.size.y = LINEBREAK_SIZE;
|
||||
int code_prev = 0;
|
||||
size_t i = 0;
|
||||
while (text[i] != 0)
|
||||
for (size_t i = 0; i < text_length; ++i)
|
||||
{
|
||||
T character = text[i++];
|
||||
T character = text[i];
|
||||
int code = (int)character;
|
||||
const int32_t hash = glyphhash(code, params.style, params.size);
|
||||
|
||||
@@ -147,20 +153,20 @@ namespace wi::font
|
||||
if (code == '\n')
|
||||
{
|
||||
word_wrap();
|
||||
line += LINEBREAK_SIZE;
|
||||
pos = 0;
|
||||
cursor.pos.x = 0;
|
||||
cursor.pos.y += LINEBREAK_SIZE;
|
||||
code_prev = 0;
|
||||
}
|
||||
else if (code == ' ')
|
||||
{
|
||||
word_wrap();
|
||||
pos += WHITESPACE_SIZE;
|
||||
cursor.pos.x += WHITESPACE_SIZE;
|
||||
code_prev = 0;
|
||||
}
|
||||
else if (code == '\t')
|
||||
{
|
||||
word_wrap();
|
||||
pos += TAB_SIZE;
|
||||
cursor.pos.x += TAB_SIZE;
|
||||
code_prev = 0;
|
||||
}
|
||||
else
|
||||
@@ -171,24 +177,26 @@ namespace wi::font
|
||||
const float glyphOffsetX = glyph.x * params.scaling;
|
||||
const float glyphOffsetY = glyph.y * params.scaling;
|
||||
|
||||
const size_t vertexID = size_t(quadCount) * 4;
|
||||
const size_t vertexID = size_t(cursor.quadCount) * 4;
|
||||
vertexList.resize(vertexID + 4);
|
||||
cursor.quadCount++;
|
||||
|
||||
if (start_new_word)
|
||||
if (cursor.start_new_word)
|
||||
{
|
||||
last_word_begin = vertexID;
|
||||
cursor.last_word_begin = vertexID;
|
||||
}
|
||||
start_new_word = false;
|
||||
cursor.start_new_word = false;
|
||||
|
||||
if (code_prev != 0)
|
||||
{
|
||||
int kern = stbtt_GetCodepointKernAdvance(&fontStyle.fontInfo, code_prev, code);
|
||||
pos += kern * fontScale;
|
||||
cursor.pos.x += kern * fontScale;
|
||||
}
|
||||
code_prev = code;
|
||||
|
||||
const float left = pos + glyphOffsetX;
|
||||
const float left = cursor.pos.x + glyphOffsetX;
|
||||
const float right = left + glyphWidth;
|
||||
const float top = line + glyphOffsetY;
|
||||
const float top = cursor.pos.y + glyphOffsetY;
|
||||
const float bottom = top + glyphHeight;
|
||||
|
||||
vertexList[vertexID + 0].pos = float2(left, top);
|
||||
@@ -201,17 +209,20 @@ namespace wi::font
|
||||
vertexList[vertexID + 2].uv = float2(glyph.tc_left, glyph.tc_bottom);
|
||||
vertexList[vertexID + 3].uv = float2(glyph.tc_right, glyph.tc_bottom);
|
||||
|
||||
pos += glyph.width * params.scaling + params.spacingX;
|
||||
pos_last_letter = pos;
|
||||
|
||||
quadCount++;
|
||||
cursor.pos.x += glyph.width * params.scaling + params.spacingX;
|
||||
}
|
||||
|
||||
cursor.size.x = std::max(cursor.size.x, cursor.pos.x);
|
||||
cursor.size.y = std::max(cursor.size.y, cursor.pos.y);
|
||||
}
|
||||
|
||||
word_wrap();
|
||||
|
||||
return quadCount;
|
||||
return cursor;
|
||||
}
|
||||
void CommitText(void* vertexList_GPU)
|
||||
{
|
||||
std::memcpy(vertexList_GPU, vertexList.data(), sizeof(FontVertex) * vertexList.size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -423,76 +434,6 @@ namespace wi::font
|
||||
return int(fontStyles.size() - 1);
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
float TextWidth_internal(const T* text, const Params& params)
|
||||
{
|
||||
if (params.style >= (int)fontStyles.size())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: account for word wrap
|
||||
float maxWidth = 0;
|
||||
float currentLineWidth = 0;
|
||||
size_t i = 0;
|
||||
while (text[i] != 0)
|
||||
{
|
||||
int code = (int)text[i++];
|
||||
const int32_t hash = glyphhash(code, params.style, params.size);
|
||||
|
||||
if (glyph_lookup.count(hash) == 0)
|
||||
{
|
||||
// glyph not packed yet, we just continue (it will be added if it is actually rendered)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (code == '\n')
|
||||
{
|
||||
currentLineWidth = 0;
|
||||
}
|
||||
else if (code == ' ')
|
||||
{
|
||||
currentLineWidth += WHITESPACE_SIZE;
|
||||
}
|
||||
else if (code == '\t')
|
||||
{
|
||||
currentLineWidth += TAB_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Glyph& glyph = glyph_lookup.at(hash);
|
||||
currentLineWidth += glyph.width + float(params.spacingX) * params.scaling;
|
||||
}
|
||||
maxWidth = std::max(maxWidth, currentLineWidth);
|
||||
}
|
||||
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
float TextHeight_internal(const T* text, const Params& params)
|
||||
{
|
||||
if (params.style >= (int)fontStyles.size())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: account for word wrap
|
||||
float height = LINEBREAK_SIZE;
|
||||
size_t i = 0;
|
||||
while (text[i] != 0)
|
||||
{
|
||||
int code = (int)text[i++];
|
||||
if (code == '\n')
|
||||
{
|
||||
height += LINEBREAK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Draw_internal(const T* text, size_t text_length, const Params& params, CommandList cmd)
|
||||
{
|
||||
@@ -500,32 +441,29 @@ namespace wi::font
|
||||
{
|
||||
return;
|
||||
}
|
||||
Cursor cursor = ParseText(text, text_length, params);
|
||||
|
||||
Params newProps = params;
|
||||
|
||||
if (params.h_align == WIFALIGN_CENTER)
|
||||
newProps.posX -= TextWidth_internal(text, newProps) / 2;
|
||||
newProps.posX -= cursor.size.x / 2;
|
||||
else if (params.h_align == WIFALIGN_RIGHT)
|
||||
newProps.posX -= TextWidth_internal(text, newProps);
|
||||
newProps.posX -= cursor.size.x;
|
||||
if (params.v_align == WIFALIGN_CENTER)
|
||||
newProps.posY -= TextHeight_internal(text, newProps) / 2;
|
||||
newProps.posY -= cursor.size.y / 2;
|
||||
else if (params.v_align == WIFALIGN_BOTTOM)
|
||||
newProps.posY -= TextHeight_internal(text, newProps);
|
||||
newProps.posY -= cursor.size.y;
|
||||
|
||||
GraphicsDevice* device = wi::graphics::GetDevice();
|
||||
|
||||
GraphicsDevice::GPUAllocation mem = device->AllocateGPU(sizeof(FontVertex) * text_length * 4, cmd);
|
||||
if (!mem.IsValid())
|
||||
if (cursor.quadCount > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
static thread_local wi::vector<FontVertex> textbuffer;
|
||||
textbuffer.resize(text_length * 4);
|
||||
const uint32_t quadCount = WriteVertices(textbuffer.data(), text, newProps);
|
||||
std::memcpy(mem.data, textbuffer.data(), textbuffer.size() * sizeof(FontVertex)); // only allow writes into mapped GPU buffer memory to avoid uncached read by mistake
|
||||
GraphicsDevice* device = wi::graphics::GetDevice();
|
||||
GraphicsDevice::GPUAllocation mem = device->AllocateGPU(sizeof(FontVertex) * cursor.quadCount * 4, cmd);
|
||||
if (!mem.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
CommitText(mem.data);
|
||||
|
||||
if (quadCount > 0)
|
||||
{
|
||||
device->EventBegin("Font", cmd);
|
||||
|
||||
device->BindPipelineState(&PSO, cmd);
|
||||
@@ -555,7 +493,7 @@ namespace wi::font
|
||||
font_push.color = newProps.shadowColor.rgba;
|
||||
device->PushConstants(&font_push, sizeof(font_push), cmd);
|
||||
|
||||
device->DrawInstanced(4, quadCount, 0, 0, cmd);
|
||||
device->DrawInstanced(4, cursor.quadCount, 0, 0, cmd);
|
||||
}
|
||||
|
||||
// font base render:
|
||||
@@ -568,7 +506,7 @@ namespace wi::font
|
||||
font_push.color = newProps.color.rgba;
|
||||
device->PushConstants(&font_push, sizeof(font_push), cmd);
|
||||
|
||||
device->DrawInstanced(4, quadCount, 0, 0, cmd);
|
||||
device->DrawInstanced(4, cursor.quadCount, 0, 0, cmd);
|
||||
|
||||
device->EventEnd(cmd);
|
||||
}
|
||||
@@ -610,36 +548,72 @@ namespace wi::font
|
||||
|
||||
float TextWidth(const char* text, const Params& params)
|
||||
{
|
||||
return TextWidth_internal(text, params);
|
||||
size_t text_length = strlen(text);
|
||||
if (text_length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text, text_length, params).size.x;
|
||||
}
|
||||
float TextWidth(const wchar_t* text, const Params& params)
|
||||
{
|
||||
return TextWidth_internal(text, params);
|
||||
size_t text_length = wcslen(text);
|
||||
if (text_length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text, text_length, params).size.x;
|
||||
}
|
||||
float TextWidth(const std::string& text, const Params& params)
|
||||
{
|
||||
return TextWidth_internal(text.c_str(), params);
|
||||
if (text.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text.c_str(), text.length(), params).size.x;
|
||||
}
|
||||
float TextWidth(const std::wstring& text, const Params& params)
|
||||
{
|
||||
return TextWidth_internal(text.c_str(), params);
|
||||
if (text.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text.c_str(), text.length(), params).size.x;
|
||||
}
|
||||
|
||||
float TextHeight(const char* text, const Params& params)
|
||||
{
|
||||
return TextHeight_internal(text, params);
|
||||
size_t text_length = strlen(text);
|
||||
if (text_length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text, text_length, params).size.y;
|
||||
}
|
||||
float TextHeight(const wchar_t* text, const Params& params)
|
||||
{
|
||||
return TextHeight_internal(text, params);
|
||||
size_t text_length = wcslen(text);
|
||||
if (text_length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text, text_length, params).size.y;
|
||||
}
|
||||
float TextHeight(const std::string& text, const Params& params)
|
||||
{
|
||||
return TextHeight_internal(text.c_str(), params);
|
||||
if (text.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text.c_str(), text.length(), params).size.y;
|
||||
}
|
||||
float TextHeight(const std::wstring& text, const Params& params)
|
||||
{
|
||||
return TextHeight_internal(text.c_str(), params);
|
||||
if (text.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ParseText(text.c_str(), text.length(), params).size.y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+206
-173
@@ -714,11 +714,190 @@ namespace wi::gui
|
||||
|
||||
|
||||
|
||||
|
||||
void ScrollBar::Update(const wi::Canvas& canvas, float dt)
|
||||
{
|
||||
if (!IsVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Widget::Update(canvas, dt);
|
||||
|
||||
if (scale.x > scale.y)
|
||||
{
|
||||
vertical = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
vertical = true;
|
||||
}
|
||||
|
||||
if (IsEnabled())
|
||||
{
|
||||
if (state == FOCUS)
|
||||
{
|
||||
state = IDLE;
|
||||
}
|
||||
if (state == DEACTIVATING)
|
||||
{
|
||||
state = IDLE;
|
||||
}
|
||||
if (state == ACTIVE)
|
||||
{
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
Hitbox2D hitbox = Hitbox2D(XMFLOAT2(translation.x, translation.y), XMFLOAT2(scale.x, scale.y));
|
||||
Hitbox2D pointerHitbox = GetPointerHitbox();
|
||||
|
||||
if (state == IDLE && hitbox.intersects(pointerHitbox))
|
||||
{
|
||||
state = FOCUS;
|
||||
}
|
||||
|
||||
bool clicked = false;
|
||||
if (wi::input::Press(wi::input::MOUSE_BUTTON_LEFT))
|
||||
{
|
||||
clicked = true;
|
||||
}
|
||||
|
||||
bool click_down = false;
|
||||
if (wi::input::Down(wi::input::MOUSE_BUTTON_LEFT))
|
||||
{
|
||||
click_down = true;
|
||||
if (state == FOCUS || state == DEACTIVATING)
|
||||
{
|
||||
// Keep pressed until mouse is released
|
||||
Activate();
|
||||
}
|
||||
}
|
||||
float scrollbar_begin;
|
||||
float scrollbar_end;
|
||||
float scrollbar_size;
|
||||
|
||||
if (vertical)
|
||||
{
|
||||
scrollbar_begin = translation.y;
|
||||
scrollbar_end = scrollbar_begin + scale.y;
|
||||
scrollbar_size = scrollbar_end - scrollbar_begin;
|
||||
scrollbar_granularity = std::min(1.0f, scrollbar_size / std::max(1.0f, list_length));
|
||||
scrollbar_length = std::max(scale.x * 2, scrollbar_size * scrollbar_granularity);
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollbar_begin = translation.x;
|
||||
scrollbar_end = scrollbar_begin + scale.x;
|
||||
scrollbar_size = scrollbar_end - scrollbar_begin;
|
||||
scrollbar_granularity = std::min(1.0f, scrollbar_size / std::max(1.0f, list_length));
|
||||
scrollbar_length = std::max(scale.y * 2, scrollbar_size * scrollbar_granularity);
|
||||
}
|
||||
|
||||
if (!click_down)
|
||||
{
|
||||
scrollbar_state = SCROLLBAR_INACTIVE;
|
||||
}
|
||||
|
||||
if (IsScrollbarRequired() && hitbox.intersects(pointerHitbox))
|
||||
{
|
||||
if (clicked)
|
||||
{
|
||||
scrollbar_state = SCROLLBAR_GRABBED;
|
||||
grab_pos = pointerHitbox.pos;
|
||||
grab_pos.x = wi::math::Clamp(grab_pos.x, scrollbar_begin + scrollbar_delta, scrollbar_begin + scrollbar_delta + scrollbar_length);
|
||||
grab_pos.y = wi::math::Clamp(grab_pos.y, scrollbar_begin + scrollbar_delta, scrollbar_begin + scrollbar_delta + scrollbar_length);
|
||||
grab_delta = scrollbar_delta;
|
||||
}
|
||||
else if (!click_down)
|
||||
{
|
||||
scrollbar_state = SCROLLBAR_HOVER;
|
||||
state = FOCUS;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollbar_state == SCROLLBAR_GRABBED)
|
||||
{
|
||||
Activate();
|
||||
if (vertical)
|
||||
{
|
||||
scrollbar_delta = grab_delta + pointerHitbox.pos.y - grab_pos.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollbar_delta = grab_delta + pointerHitbox.pos.x - grab_pos.x;
|
||||
}
|
||||
}
|
||||
|
||||
scrollbar_delta = wi::math::Clamp(scrollbar_delta, 0, scrollbar_size - scrollbar_length);
|
||||
if (scrollbar_begin < scrollbar_end - scrollbar_length)
|
||||
{
|
||||
scrollbar_value = wi::math::InverseLerp(scrollbar_begin, scrollbar_end - scrollbar_length, scrollbar_begin + scrollbar_delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollbar_value = 0;
|
||||
}
|
||||
|
||||
list_offset = -scrollbar_value * (list_length - scrollbar_size * (1.0f - overscroll));
|
||||
}
|
||||
|
||||
if (vertical)
|
||||
{
|
||||
for (int i = 0; i < arraysize(sprites_knob); ++i)
|
||||
{
|
||||
sprites_knob[i].params.pos.x = translation.x + knob_inset_border.x;
|
||||
sprites_knob[i].params.pos.y = translation.y + knob_inset_border.y + scrollbar_delta;
|
||||
sprites_knob[i].params.siz.x = scale.x - knob_inset_border.x * 2;
|
||||
sprites_knob[i].params.siz.y = scrollbar_length - knob_inset_border.y * 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < arraysize(sprites_knob); ++i)
|
||||
{
|
||||
sprites_knob[i].params.pos.x = translation.x + knob_inset_border.x + scrollbar_delta;
|
||||
sprites_knob[i].params.pos.y = translation.y + knob_inset_border.y;
|
||||
sprites_knob[i].params.siz.x = scrollbar_length - knob_inset_border.x * 2;
|
||||
sprites_knob[i].params.siz.y = scale.y - knob_inset_border.y * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ScrollBar::Render(const wi::Canvas& canvas, CommandList cmd) const
|
||||
{
|
||||
if (!IsVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!IsScrollbarRequired())
|
||||
return;
|
||||
|
||||
// scrollbar background
|
||||
wi::image::Params fx = sprites[state].params;
|
||||
fx.pos = XMFLOAT3(translation.x, translation.y, 0);
|
||||
fx.siz = XMFLOAT2(scale.x, scale.y);
|
||||
fx.color = sprites[IDLE].params.color;
|
||||
wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd);
|
||||
|
||||
// scrollbar knob
|
||||
sprites_knob[scrollbar_state].Draw(cmd);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Label::Create(const std::string& name)
|
||||
{
|
||||
SetName(name);
|
||||
SetText(name);
|
||||
SetSize(XMFLOAT2(100, 20));
|
||||
|
||||
scrollbar.SetColor(wi::Color(80, 80, 80, 100), wi::gui::IDLE);
|
||||
scrollbar.sprites_knob[ScrollBar::SCROLLBAR_INACTIVE].params.color = wi::Color(140, 140, 140, 140);
|
||||
scrollbar.sprites_knob[ScrollBar::SCROLLBAR_HOVER].params.color = wi::Color(180, 180, 180, 180);
|
||||
scrollbar.sprites_knob[ScrollBar::SCROLLBAR_GRABBED].params.color = wi::Color::White();
|
||||
scrollbar.SetOverScroll(0.25f);
|
||||
scrollbar.knob_inset_border = XMFLOAT2(4, 2);
|
||||
}
|
||||
void Label::Update(const wi::Canvas& canvas, float dt)
|
||||
{
|
||||
@@ -752,6 +931,31 @@ namespace wi::gui
|
||||
font.params.posY = translation.y + scale.y * 0.5f;
|
||||
break;
|
||||
}
|
||||
|
||||
scrollbar.SetListLength(font.TextHeight());
|
||||
scrollbar.ClearTransform();
|
||||
scrollbar.SetPos(XMFLOAT2(translation.x + scale.x - scrollbar_width, translation.y));
|
||||
scrollbar.SetSize(XMFLOAT2(scrollbar_width, scale.y));
|
||||
scrollbar.Update(canvas, dt);
|
||||
|
||||
Hitbox2D pointerHitbox = GetPointerHitbox();
|
||||
if (pointerHitbox.intersects(hitBox) && !force_disable)
|
||||
{
|
||||
state = FOCUS;
|
||||
// This is outside scrollbar code, because it can also be scrolled if parent widget is only in focus
|
||||
scrollbar.Scroll(wi::input::GetPointer().z * 20);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = IDLE;
|
||||
}
|
||||
|
||||
if (scrollbar.IsScrollbarRequired())
|
||||
{
|
||||
font.params.h_wrap = scale.x - scrollbar_width;
|
||||
}
|
||||
|
||||
font.params.posY += scrollbar.GetOffset();
|
||||
}
|
||||
void Label::Render(const wi::Canvas& canvas, CommandList cmd) const
|
||||
{
|
||||
@@ -766,6 +970,8 @@ namespace wi::gui
|
||||
|
||||
sprites[state].Draw(cmd);
|
||||
font.Draw(cmd);
|
||||
|
||||
scrollbar.Render(canvas, cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -1717,179 +1923,6 @@ namespace wi::gui
|
||||
|
||||
|
||||
|
||||
void ScrollBar::Update(const wi::Canvas& canvas, float dt)
|
||||
{
|
||||
if (!IsVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Widget::Update(canvas, dt);
|
||||
|
||||
if (scale.x > scale.y)
|
||||
{
|
||||
vertical = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
vertical = true;
|
||||
}
|
||||
|
||||
if (IsEnabled())
|
||||
{
|
||||
if (state == FOCUS)
|
||||
{
|
||||
state = IDLE;
|
||||
}
|
||||
if (state == DEACTIVATING)
|
||||
{
|
||||
state = IDLE;
|
||||
}
|
||||
if (state == ACTIVE)
|
||||
{
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
Hitbox2D hitbox = Hitbox2D(XMFLOAT2(translation.x, translation.y), XMFLOAT2(scale.x, scale.y));
|
||||
Hitbox2D pointerHitbox = GetPointerHitbox();
|
||||
|
||||
if (state == IDLE && hitbox.intersects(pointerHitbox))
|
||||
{
|
||||
state = FOCUS;
|
||||
}
|
||||
|
||||
bool clicked = false;
|
||||
if (wi::input::Press(wi::input::MOUSE_BUTTON_LEFT))
|
||||
{
|
||||
clicked = true;
|
||||
}
|
||||
|
||||
bool click_down = false;
|
||||
if (wi::input::Down(wi::input::MOUSE_BUTTON_LEFT))
|
||||
{
|
||||
click_down = true;
|
||||
if (state == FOCUS || state == DEACTIVATING)
|
||||
{
|
||||
// Keep pressed until mouse is released
|
||||
Activate();
|
||||
}
|
||||
}
|
||||
float scrollbar_begin;
|
||||
float scrollbar_end;
|
||||
float scrollbar_size;
|
||||
|
||||
if (vertical)
|
||||
{
|
||||
scrollbar_begin = translation.y;
|
||||
scrollbar_end = scrollbar_begin + scale.y;
|
||||
scrollbar_size = scrollbar_end - scrollbar_begin;
|
||||
scrollbar_granularity = std::min(1.0f, scrollbar_size / std::max(1.0f, list_length));
|
||||
scrollbar_length = std::max(scale.x * 2, scrollbar_size * scrollbar_granularity);
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollbar_begin = translation.x;
|
||||
scrollbar_end = scrollbar_begin + scale.x;
|
||||
scrollbar_size = scrollbar_end - scrollbar_begin;
|
||||
scrollbar_granularity = std::min(1.0f, scrollbar_size / std::max(1.0f, list_length));
|
||||
scrollbar_length = std::max(scale.y * 2, scrollbar_size * scrollbar_granularity);
|
||||
}
|
||||
|
||||
if (!click_down)
|
||||
{
|
||||
scrollbar_state = SCROLLBAR_INACTIVE;
|
||||
}
|
||||
|
||||
if (IsScrollbarRequired() && hitbox.intersects(pointerHitbox))
|
||||
{
|
||||
if (clicked)
|
||||
{
|
||||
scrollbar_state = SCROLLBAR_GRABBED;
|
||||
grab_pos = pointerHitbox.pos;
|
||||
grab_pos.x = wi::math::Clamp(grab_pos.x, scrollbar_begin + scrollbar_delta, scrollbar_begin + scrollbar_delta + scrollbar_length);
|
||||
grab_pos.y = wi::math::Clamp(grab_pos.y, scrollbar_begin + scrollbar_delta, scrollbar_begin + scrollbar_delta + scrollbar_length);
|
||||
grab_delta = scrollbar_delta;
|
||||
}
|
||||
else if (!click_down)
|
||||
{
|
||||
scrollbar_state = SCROLLBAR_HOVER;
|
||||
state = FOCUS;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollbar_state == SCROLLBAR_GRABBED)
|
||||
{
|
||||
Activate();
|
||||
if (vertical)
|
||||
{
|
||||
scrollbar_delta = grab_delta + pointerHitbox.pos.y - grab_pos.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollbar_delta = grab_delta + pointerHitbox.pos.x - grab_pos.x;
|
||||
}
|
||||
}
|
||||
|
||||
scrollbar_delta = wi::math::Clamp(scrollbar_delta, 0, scrollbar_size - scrollbar_length);
|
||||
if (scrollbar_begin < scrollbar_end - scrollbar_length)
|
||||
{
|
||||
scrollbar_value = wi::math::InverseLerp(scrollbar_begin, scrollbar_end - scrollbar_length, scrollbar_begin + scrollbar_delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollbar_value = 0;
|
||||
}
|
||||
|
||||
list_offset = -scrollbar_value * (list_length - scrollbar_size * (1.0f - overscroll));
|
||||
}
|
||||
|
||||
if (vertical)
|
||||
{
|
||||
for (int i = 0; i < arraysize(sprites_knob); ++i)
|
||||
{
|
||||
sprites_knob[i].params.pos.x = translation.x + knob_inset_border.x;
|
||||
sprites_knob[i].params.pos.y = translation.y + knob_inset_border.y + scrollbar_delta;
|
||||
sprites_knob[i].params.siz.x = scale.x - knob_inset_border.x * 2;
|
||||
sprites_knob[i].params.siz.y = scrollbar_length - knob_inset_border.y * 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < arraysize(sprites_knob); ++i)
|
||||
{
|
||||
sprites_knob[i].params.pos.x = translation.x + knob_inset_border.x + scrollbar_delta;
|
||||
sprites_knob[i].params.pos.y = translation.y + knob_inset_border.y;
|
||||
sprites_knob[i].params.siz.x = scrollbar_length - knob_inset_border.x * 2;
|
||||
sprites_knob[i].params.siz.y = scale.y - knob_inset_border.y * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ScrollBar::Render(const wi::Canvas& canvas, CommandList cmd) const
|
||||
{
|
||||
if (!IsVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!IsScrollbarRequired())
|
||||
return;
|
||||
|
||||
// scrollbar background
|
||||
wi::image::Params fx = sprites[state].params;
|
||||
fx.pos = XMFLOAT3(translation.x, translation.y, 0);
|
||||
fx.siz = XMFLOAT2(scale.x, scale.y);
|
||||
fx.color = sprites[IDLE].params.color;
|
||||
wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd);
|
||||
|
||||
// scrollbar knob
|
||||
sprites_knob[scrollbar_state].Draw(cmd);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static const float windowcontrolSize = 20.0f;
|
||||
void Window::Create(const std::string& name, bool window_controls)
|
||||
{
|
||||
|
||||
+46
-44
@@ -152,10 +152,56 @@ namespace wi::gui
|
||||
void OnDragEnd(std::function<void(EventArgs args)> func);
|
||||
};
|
||||
|
||||
// Generic scroll bar
|
||||
class ScrollBar : public Widget
|
||||
{
|
||||
protected:
|
||||
float scrollbar_delta = 0;
|
||||
float scrollbar_length = 0;
|
||||
float scrollbar_value = 0;
|
||||
float scrollbar_granularity = 1;
|
||||
float list_length = 0;
|
||||
float list_offset = 0;
|
||||
float overscroll = 0;
|
||||
bool vertical = true;
|
||||
XMFLOAT2 grab_pos = {};
|
||||
float grab_delta = 0;
|
||||
|
||||
public:
|
||||
// Set the list's length that will be scrollable and moving
|
||||
void SetListLength(float size) { list_length = size; }
|
||||
// The scrolling offset that should be applied to the list items
|
||||
float GetOffset() const { return list_offset; }
|
||||
// This can be called by user for extra scrolling on top of base functionality
|
||||
void Scroll(float amount) { scrollbar_delta -= amount; }
|
||||
// How much the max scrolling will offset the list even further than it would be necessary for fitting
|
||||
// this value is in percent of a full scrollbar's worth of extra offset
|
||||
// 0: no extra offset
|
||||
// 1: full extra offset
|
||||
void SetOverScroll(float amount) { overscroll = amount; }
|
||||
// Check whether the scrollbar is required (when the items don't and scrolling could be used)
|
||||
bool IsScrollbarRequired() const { return scrollbar_granularity < 1; }
|
||||
|
||||
enum SCROLLBAR_STATE
|
||||
{
|
||||
SCROLLBAR_INACTIVE,
|
||||
SCROLLBAR_HOVER,
|
||||
SCROLLBAR_GRABBED,
|
||||
SCROLLBAR_STATE_COUNT,
|
||||
} scrollbar_state = SCROLLBAR_INACTIVE;
|
||||
wi::Sprite sprites_knob[SCROLLBAR_STATE_COUNT];
|
||||
XMFLOAT2 knob_inset_border = {};
|
||||
|
||||
void Update(const wi::Canvas& canvas, float dt) override;
|
||||
void Render(const wi::Canvas& canvas, wi::graphics::CommandList cmd) const override;
|
||||
};
|
||||
|
||||
// Static box that holds text
|
||||
class Label : public Widget
|
||||
{
|
||||
protected:
|
||||
ScrollBar scrollbar;
|
||||
float scrollbar_width = 18;
|
||||
public:
|
||||
void Create(const std::string& name);
|
||||
|
||||
@@ -297,50 +343,6 @@ namespace wi::gui
|
||||
void OnSelect(std::function<void(EventArgs args)> func);
|
||||
};
|
||||
|
||||
// Generic scroll bar
|
||||
class ScrollBar : public Widget
|
||||
{
|
||||
protected:
|
||||
float scrollbar_delta = 0;
|
||||
float scrollbar_length = 0;
|
||||
float scrollbar_value = 0;
|
||||
float scrollbar_granularity = 1;
|
||||
float list_length = 0;
|
||||
float list_offset = 0;
|
||||
float overscroll = 0;
|
||||
bool vertical = true;
|
||||
XMFLOAT2 grab_pos = {};
|
||||
float grab_delta = 0;
|
||||
|
||||
public:
|
||||
// Set the list's length that will be scrollable and moving
|
||||
void SetListLength(float size) { list_length = size; }
|
||||
// The scrolling offset that should be applied to the list items
|
||||
float GetOffset() const { return list_offset; }
|
||||
// This can be called by user for extra scrolling on top of base functionality
|
||||
void Scroll(float amount) { scrollbar_delta -= amount; }
|
||||
// How much the max scrolling will offset the list even further than it would be necessary for fitting
|
||||
// this value is in percent of a full scrollbar's worth of extra offset
|
||||
// 0: no extra offset
|
||||
// 1: full extra offset
|
||||
void SetOverScroll(float amount) { overscroll = amount; }
|
||||
// Check whether the scrollbar is required (when the items don't and scrolling could be used)
|
||||
bool IsScrollbarRequired() const { return scrollbar_granularity < 1; }
|
||||
|
||||
enum SCROLLBAR_STATE
|
||||
{
|
||||
SCROLLBAR_INACTIVE,
|
||||
SCROLLBAR_HOVER,
|
||||
SCROLLBAR_GRABBED,
|
||||
SCROLLBAR_STATE_COUNT,
|
||||
} scrollbar_state = SCROLLBAR_INACTIVE;
|
||||
wi::Sprite sprites_knob[SCROLLBAR_STATE_COUNT];
|
||||
XMFLOAT2 knob_inset_border = {};
|
||||
|
||||
void Update(const wi::Canvas& canvas, float dt) override;
|
||||
void Render(const wi::Canvas& canvas, wi::graphics::CommandList cmd) const override;
|
||||
};
|
||||
|
||||
// Widget container
|
||||
class Window :public Widget
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace wi::version
|
||||
// minor features, major updates, breaking compatibility changes
|
||||
const int minor = 60;
|
||||
// minor bug fixes, alterations, refactors, updates
|
||||
const int revision = 70;
|
||||
const int revision = 71;
|
||||
|
||||
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user