You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
9.0 KiB
302 lines
9.0 KiB
/* |
|
* EEZ Modular Firmware |
|
* Copyright (C) 2015-present, Envox d.o.o. |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
|
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
|
|
* You should have received a copy of the GNU General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include <stdio.h> |
|
|
|
#include <eez/core/util.h> |
|
#include <eez/core/sound.h> |
|
|
|
#include <eez/gui/gui.h> |
|
#include <eez/gui/widgets/roller.h> |
|
|
|
#include <eez/flow/components/roller_widget.h> |
|
|
|
static const float FRICTION = 0.1f; |
|
static const float BOUNCE_FORCE = 0.1f; |
|
|
|
namespace eez { |
|
namespace gui { |
|
|
|
bool RollerWidgetState::updateState() { |
|
WIDGET_STATE_START(RollerWidget); |
|
|
|
const Style* selectedValueStyle = getStyle(widget->selectedValueStyle); |
|
font::Font fontSelectedValue = styleGetFont(selectedValueStyle); |
|
if (!fontSelectedValue) { |
|
return true; |
|
} |
|
|
|
textHeight = selectedValueStyle->paddingTop + fontSelectedValue.getHeight() + selectedValueStyle->paddingBottom; |
|
|
|
minValue = get(widgetCursor, widget->min).toInt32(); |
|
maxValue = get(widgetCursor, widget->max).toInt32(); |
|
|
|
Value dataValue = get(widgetCursor, widget->data); |
|
|
|
if (widgetCursor.flowState) { |
|
auto executionState = (flow::RollerWidgetComponenentExecutionState *)widgetCursor.flowState->componenentExecutionStates[widget->componentIndex]; |
|
if (executionState && executionState->clear) { |
|
executionState->clear = false; |
|
isRunning = true; |
|
velocity = 0; |
|
applyForce(-1.5f * position * FRICTION); |
|
} |
|
} |
|
|
|
float oldPosition = position; |
|
|
|
if (isRunning) { |
|
updatePosition(); |
|
|
|
auto newDataValue = minValue + int(roundf(-position / textHeight)); |
|
if (newDataValue < minValue) { |
|
newDataValue = minValue; |
|
} else if (newDataValue > maxValue) { |
|
newDataValue = maxValue; |
|
} |
|
|
|
if (newDataValue != dataValue.getInt()) { |
|
dataValue = newDataValue; |
|
set(widgetCursor, widget->data, data); |
|
sound::playClick(); |
|
} |
|
|
|
if (!isMoving()) { |
|
isRunning = false; |
|
} |
|
} else { |
|
position = -(dataValue.getInt() - minValue) * textHeight; |
|
} |
|
|
|
if (oldPosition != position) { |
|
hasPreviousState = false; |
|
} |
|
|
|
WIDGET_STATE(data, dataValue); |
|
|
|
WIDGET_STATE_END() |
|
} |
|
|
|
void RollerWidgetState::render() { |
|
const WidgetCursor &widgetCursor = g_widgetCursor; |
|
auto widget = (const RollerWidget *)widgetCursor.widget; |
|
const Style* style = getStyle(widget->style); |
|
const Style* selectedValueStyle = getStyle(widget->selectedValueStyle); |
|
const Style* unselectedValueStyle = getStyle(widget->unselectedValueStyle); |
|
|
|
font::Font fontSelectedValue = styleGetFont(selectedValueStyle); |
|
if (!fontSelectedValue) { |
|
return; |
|
} |
|
|
|
font::Font fontUnselectedValue = styleGetFont(unselectedValueStyle); |
|
if (!fontUnselectedValue) { |
|
return; |
|
} |
|
|
|
drawRectangle( |
|
widgetCursor.x, widgetCursor.y, widgetCursor.w, widgetCursor.h, |
|
style |
|
); |
|
|
|
display::AggDrawing aggDrawing; |
|
display::aggInit(aggDrawing); |
|
|
|
auto x = widgetCursor.x; |
|
|
|
int rectHeight = selectedValueStyle->borderSizeTop + textHeight + selectedValueStyle->borderSizeBottom; |
|
|
|
display::setColor(selectedValueStyle->borderColor); |
|
auto yCenter = widgetCursor.y + (widgetCursor.h - rectHeight) / 2; |
|
display::drawRoundedRect( |
|
aggDrawing, |
|
x, yCenter, x + widgetCursor.w - 1, yCenter + rectHeight - 1, selectedValueStyle->borderSizeLeft, |
|
selectedValueStyle->borderRadiusTLX, selectedValueStyle->borderRadiusTLY, selectedValueStyle->borderRadiusTRX, selectedValueStyle->borderRadiusTRY, |
|
selectedValueStyle->borderRadiusBRX, selectedValueStyle->borderRadiusBRY, selectedValueStyle->borderRadiusBLX, selectedValueStyle->borderRadiusBLY |
|
); |
|
|
|
int clip1_x1 = widgetCursor.x; |
|
int clip1_y1 = widgetCursor.y; |
|
int clip1_x2 = widgetCursor.x + widgetCursor.w - 1; |
|
int clip1_y2 = yCenter - 1; |
|
|
|
int clip2_x1 = widgetCursor.x; |
|
int clip2_y1 = yCenter + selectedValueStyle->borderSizeTop; |
|
int clip2_x2 = widgetCursor.x + widgetCursor.w - 1; |
|
int clip2_y2 = yCenter + rectHeight - selectedValueStyle->borderSizeBottom; |
|
|
|
int clip3_x1 = widgetCursor.x; |
|
int clip3_y1 = yCenter + rectHeight; |
|
int clip3_x2 = widgetCursor.x + widgetCursor.w - 1; |
|
int clip3_y2 = widgetCursor.y + widgetCursor.h - 1; |
|
|
|
auto y = widgetCursor.y + (widgetCursor.h - textHeight) / 2 + (int)roundf(position); |
|
|
|
display::setColor(unselectedValueStyle->color); |
|
|
|
for (int i = minValue; i <= maxValue; i++, y += textHeight) { |
|
if (y + textHeight <= widgetCursor.y) { |
|
continue; |
|
} |
|
if (y >= widgetCursor.y + widgetCursor.h) { |
|
break; |
|
} |
|
|
|
set(widgetCursor, widget->data, i); |
|
|
|
Value textValue = get(widgetCursor, widget->text); |
|
|
|
char text[100]; |
|
textValue.toText(text, sizeof(text)); |
|
|
|
auto textLength = strlen(text); |
|
|
|
int textWidth = display::measureStr(text, textLength, fontUnselectedValue, 0); |
|
if (y < clip1_y2) { |
|
display::drawStr( |
|
text, textLength, |
|
x + (widgetCursor.w - textWidth) / 2, y, |
|
clip1_x1, clip1_y1, clip1_x2, clip1_y2, |
|
fontUnselectedValue, |
|
-1 |
|
); |
|
} else { |
|
display::drawStr( |
|
text, textLength, |
|
x + (widgetCursor.w - textWidth) / 2, y, |
|
clip3_x1, clip3_y1, clip3_x2, clip3_y2, |
|
fontUnselectedValue, |
|
-1 |
|
); |
|
} |
|
|
|
if (y + textHeight >= clip2_y1 || y < clip2_y2) { |
|
int textWidth = display::measureStr(text, textLength, fontSelectedValue, 0); |
|
|
|
display::setColor(selectedValueStyle->color); |
|
display::drawStr( |
|
text, textLength, |
|
x + (widgetCursor.w - textWidth) / 2, y, |
|
clip2_x1, clip2_y1, clip2_x2, clip2_y2, |
|
fontSelectedValue, |
|
-1 |
|
); |
|
display::setColor(unselectedValueStyle->color); |
|
} |
|
} |
|
|
|
set(widgetCursor, widget->data, data); |
|
} |
|
|
|
bool RollerWidgetState::hasOnTouch() { |
|
return true; |
|
} |
|
|
|
void RollerWidgetState::onTouch(const WidgetCursor &widgetCursor, Event &touchEvent) { |
|
if (touchEvent.type == EVENT_TYPE_TOUCH_DOWN) { |
|
dragOrigin = touchEvent.y; |
|
dragStartPosition = position; |
|
dragPosition = dragStartPosition; |
|
} else if (touchEvent.type == EVENT_TYPE_TOUCH_MOVE) { |
|
if (abs(touchEvent.y - dragOrigin) > (int)(DISPLAY_HEIGHT / 25)) { |
|
isDragging = true; |
|
isRunning = true; |
|
dragPosition = dragStartPosition + (touchEvent.y - dragOrigin); |
|
} |
|
} else if (touchEvent.type == EVENT_TYPE_TOUCH_UP || touchEvent.type == EVENT_TYPE_AUTO_REPEAT) { |
|
if (!isDragging) { |
|
isRunning = true; |
|
if (touchEvent.y < widgetCursor.y + (widgetCursor.h - textHeight) / 2) { |
|
applyForce(textHeight * FRICTION); |
|
} else if (touchEvent.y > widgetCursor.y + (widgetCursor.h - textHeight) / 2 + textHeight) { |
|
applyForce(-textHeight * FRICTION); |
|
} |
|
} else { |
|
if (touchEvent.type == EVENT_TYPE_TOUCH_UP) { |
|
isDragging = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void RollerWidgetState::updatePosition() { |
|
applyDragForce(); |
|
applySnapForce(); |
|
applyEdgeForce(); |
|
|
|
auto inverseFriction = 1.0f - FRICTION; |
|
velocity *= inverseFriction; |
|
position += velocity; |
|
} |
|
|
|
bool RollerWidgetState::isMoving() { |
|
return isDragging || fabs(velocity) >= 0.01f; |
|
} |
|
|
|
void RollerWidgetState::applyDragForce() { |
|
if (!isDragging) { |
|
return; |
|
} |
|
auto dragVelocity = dragPosition - position; |
|
applyForce(dragVelocity - velocity); |
|
} |
|
|
|
void RollerWidgetState::applySnapForce() { |
|
if (isDragging) { |
|
return; |
|
} |
|
|
|
// Make sure position ends at multiply of textHeight |
|
auto restPosition = position + velocity / FRICTION; |
|
auto targetRestPosition = roundf(restPosition / textHeight) * textHeight; |
|
applyForce(FRICTION * (targetRestPosition - position) - velocity); |
|
} |
|
|
|
void RollerWidgetState::applyEdgeForce() { |
|
if (isDragging) { |
|
return; |
|
} |
|
|
|
float edgeFrom = -(maxValue - minValue) * textHeight; |
|
float edgeTo = 0; |
|
|
|
if (position > edgeTo) { |
|
auto distanceToEdge = edgeTo - position; |
|
auto force = distanceToEdge * BOUNCE_FORCE; |
|
auto restPosition = position + (velocity + force) / FRICTION; |
|
if (restPosition <= edgeTo) { |
|
force -= velocity; |
|
} |
|
applyForce(force); |
|
} else if (position < edgeFrom) { |
|
auto distanceToEdge = edgeFrom - position; |
|
auto force = distanceToEdge * BOUNCE_FORCE; |
|
auto restPosition = position + (velocity + force) / FRICTION; |
|
if (restPosition >= edgeFrom) { |
|
force -= velocity; |
|
} |
|
applyForce(force); |
|
} |
|
} |
|
|
|
void RollerWidgetState::applyForce(float force) { |
|
velocity += force; |
|
} |
|
|
|
} // namespace gui |
|
} // namespace eez
|
|
|