mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-05-25 01:41:28 -04:00
Initial firmware commit.
This commit is contained in:
parent
626e863257
commit
dc6fee8370
357 changed files with 83134 additions and 0 deletions
558
firmware/common/ui_widget.cpp
Normal file
558
firmware/common/ui_widget.cpp
Normal file
|
@ -0,0 +1,558 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* 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 2, 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; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
constexpr size_t to_string_max_length = 16;
|
||||
|
||||
static char* to_string_dec_uint_internal(
|
||||
char* p,
|
||||
uint32_t n
|
||||
) {
|
||||
*p = 0;
|
||||
auto q = p;
|
||||
|
||||
do {
|
||||
const uint32_t d = n % 10;
|
||||
const char c = d + 48;
|
||||
*(--q) = c;
|
||||
n /= 10;
|
||||
} while( n != 0 );
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static char* to_string_dec_uint_pad_internal(
|
||||
char* const term,
|
||||
const uint32_t n,
|
||||
const int32_t l,
|
||||
const char fill
|
||||
) {
|
||||
auto q = to_string_dec_uint_internal(term, n);
|
||||
|
||||
if( fill ) {
|
||||
while( (term - q) < l ) {
|
||||
*(--q) = fill;
|
||||
}
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
std::string to_string_dec_uint(
|
||||
const uint32_t n,
|
||||
const int32_t l,
|
||||
const char fill
|
||||
) {
|
||||
char p[16];
|
||||
auto term = p + sizeof(p) - 1;
|
||||
auto q = to_string_dec_uint_pad_internal(term, n, l, fill);
|
||||
|
||||
// Right justify.
|
||||
while( (term - q) < l ) {
|
||||
*(--q) = ' ';
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
std::string to_string_dec_int(
|
||||
const int32_t n,
|
||||
const int32_t l,
|
||||
const char fill
|
||||
) {
|
||||
const size_t negative = (n < 0) ? 1 : 0;
|
||||
uint32_t n_abs = negative ? -n : n;
|
||||
|
||||
char p[16];
|
||||
auto term = p + sizeof(p) - 1;
|
||||
auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill);
|
||||
|
||||
// Add sign.
|
||||
if( negative ) {
|
||||
*(--q) = '-';
|
||||
}
|
||||
|
||||
// Right justify.
|
||||
while( (term - q) < l ) {
|
||||
*(--q) = ' ';
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static void to_string_hex_internal(char* p, const uint32_t n, const int32_t l) {
|
||||
const uint32_t d = n & 0xf;
|
||||
p[l] = (d > 9) ? (d + 87) : (d + 48);
|
||||
if( l > 0 ) {
|
||||
to_string_hex_internal(p, n >> 4, l - 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::string to_string_hex(const uint32_t n, const int32_t l) {
|
||||
char p[16];
|
||||
to_string_hex_internal(p, n, l - 1);
|
||||
p[l] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Widget ****************************************************************/
|
||||
|
||||
Point Widget::screen_pos() {
|
||||
return parent() ? (parent()->screen_pos() + parent_rect.pos) : parent_rect.pos;
|
||||
}
|
||||
|
||||
Size Widget::size() const {
|
||||
return parent_rect.size;
|
||||
}
|
||||
|
||||
Rect Widget::screen_rect() {
|
||||
return parent() ? (parent_rect + parent()->screen_pos()) : parent_rect;
|
||||
}
|
||||
|
||||
void Widget::set_parent_rect(const Rect new_parent_rect) {
|
||||
parent_rect = new_parent_rect;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
Widget* Widget::parent() const {
|
||||
return parent_;
|
||||
}
|
||||
|
||||
void Widget::set_parent(Widget* const widget) {
|
||||
if( parent_ && !widget ) {
|
||||
// We have a parent, but are losing it. Update visible status.
|
||||
visible(false);
|
||||
}
|
||||
|
||||
parent_ = widget;
|
||||
}
|
||||
|
||||
void Widget::set_dirty() {
|
||||
flags.dirty = true;
|
||||
dirty_event();
|
||||
}
|
||||
|
||||
bool Widget::dirty() const {
|
||||
return flags.dirty;
|
||||
}
|
||||
|
||||
void Widget::set_clean() {
|
||||
flags.dirty = false;
|
||||
}
|
||||
|
||||
void Widget::hidden(bool hide) {
|
||||
if( hide != flags.hidden ) {
|
||||
flags.hidden = hide;
|
||||
|
||||
// If parent is hidden, either of these is a no-op.
|
||||
if( hide ) {
|
||||
parent()->set_dirty();
|
||||
/* TODO: Notify self and all non-hidden children that they're
|
||||
* now effectively hidden?
|
||||
*/
|
||||
} else {
|
||||
set_dirty();
|
||||
/* TODO: Notify self and all non-hidden children that they're
|
||||
* now effectively shown?
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::focus() {
|
||||
context().focus_manager.set_focus_widget(this);
|
||||
}
|
||||
|
||||
void Widget::on_focus() {
|
||||
//set_dirty();
|
||||
}
|
||||
|
||||
void Widget::blur() {
|
||||
context().focus_manager.set_focus_widget(nullptr);
|
||||
}
|
||||
|
||||
void Widget::on_blur() {
|
||||
//set_dirty();
|
||||
}
|
||||
|
||||
bool Widget::focusable() const {
|
||||
return flags.focusable;
|
||||
}
|
||||
|
||||
bool Widget::has_focus() {
|
||||
return (context().focus_manager.focus_widget() == this);
|
||||
}
|
||||
|
||||
Widget* Widget::last_child_focus() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Widget::set_last_child_focus(Widget* const child) {
|
||||
// Ignore.
|
||||
(void)child;
|
||||
}
|
||||
|
||||
bool Widget::on_key(const KeyEvent event) {
|
||||
(void)event;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::on_encoder(const EncoderEvent event) {
|
||||
(void)event;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::on_touch(const TouchEvent event) {
|
||||
(void)event;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<Widget*> Widget::children() const {
|
||||
return { };
|
||||
}
|
||||
|
||||
Context& Widget::context() const {
|
||||
return parent()->context();
|
||||
}
|
||||
|
||||
void Widget::set_style(const Style* new_style) {
|
||||
if( new_style != style_ ) {
|
||||
style_ = new_style;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
const Style& Widget::style() const {
|
||||
return style_ ? *style_ : parent()->style();
|
||||
}
|
||||
|
||||
void Widget::visible(bool v) {
|
||||
if( v != flags.visible ) {
|
||||
flags.visible = v;
|
||||
|
||||
/* TODO: This on_show/on_hide implementation seems inelegant.
|
||||
* But I need *some* way to take/configure resources when
|
||||
* a widget becomes visible, and reverse the process when the
|
||||
* widget becomes invisible, whether the widget (or parent) is
|
||||
* hidden, or the widget (or parent) is removed from the tree.
|
||||
*/
|
||||
if( v ) {
|
||||
on_show();
|
||||
} else {
|
||||
on_hide();
|
||||
|
||||
// Set all children invisible too.
|
||||
for(const auto child : children()) {
|
||||
child->visible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* View ******************************************************************/
|
||||
|
||||
void View::set_parent_rect(const Rect new_parent_rect) {
|
||||
Widget::set_parent_rect(new_parent_rect);
|
||||
dirty_screen_rect += screen_rect();
|
||||
}
|
||||
|
||||
void View::paint(Painter& painter) {
|
||||
painter.fill_rectangle(
|
||||
screen_rect(),
|
||||
style().background
|
||||
);
|
||||
}
|
||||
|
||||
void View::add_child(Widget* const widget) {
|
||||
if( widget->parent() == nullptr ) {
|
||||
widget->set_parent(this);
|
||||
children_.push_back(widget);
|
||||
widget->set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void View::add_children(const std::vector<Widget*>& children) {
|
||||
for(auto child : children) {
|
||||
add_child(child);
|
||||
}
|
||||
}
|
||||
|
||||
void View::remove_child(Widget* const widget) {
|
||||
children_.erase(std::remove(children_.begin(), children_.end(), widget), children_.end());
|
||||
dirty_screen_rect += widget->screen_rect();
|
||||
widget->set_parent(nullptr);
|
||||
if( dirty_screen_rect ) {
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Widget*> View::children() const {
|
||||
return children_;
|
||||
}
|
||||
|
||||
Widget* View::initial_focus() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Rectangle *************************************************************/
|
||||
|
||||
void Rectangle::set_color(const Color c) {
|
||||
color = c;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void Rectangle::paint(Painter& painter) {
|
||||
painter.fill_rectangle(
|
||||
screen_rect(),
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
/* Text ******************************************************************/
|
||||
|
||||
void Text::set(const std::string value) {
|
||||
text = value;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void Text::paint(Painter& painter) {
|
||||
painter.draw_string(
|
||||
screen_pos(),
|
||||
style(),
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
/* Button ****************************************************************/
|
||||
|
||||
void Button::set_text(const std::string value) {
|
||||
text_ = value;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
std::string Button::text() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void Button::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
const auto paint_style = (has_focus() || flags.highlighted) ? style().invert() : style();
|
||||
|
||||
painter.draw_rectangle(r, style().foreground);
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
static_cast<Coord>(r.pos.x + 1), static_cast<Coord>(r.pos.y + 1),
|
||||
static_cast<Dim>(r.size.w - 2), static_cast<Dim>(r.size.h - 2)
|
||||
},
|
||||
paint_style.background
|
||||
);
|
||||
|
||||
const auto label_r = paint_style.font.size_of(text_);
|
||||
painter.draw_string(
|
||||
{
|
||||
static_cast<Coord>(r.pos.x + (r.size.w - label_r.w) / 2),
|
||||
static_cast<Coord>(r.pos.y + (r.size.h - label_r.h) / 2)
|
||||
},
|
||||
paint_style,
|
||||
text_
|
||||
);
|
||||
}
|
||||
|
||||
bool Button::on_key(const KeyEvent key) {
|
||||
if( key == KeyEvent::Select ) {
|
||||
if( on_select ) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Button::on_touch(const TouchEvent event) {
|
||||
switch(event.type) {
|
||||
case TouchEvent::Type::Start:
|
||||
flags.highlighted = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
|
||||
|
||||
case TouchEvent::Type::End:
|
||||
flags.highlighted = false;
|
||||
set_dirty();
|
||||
if( on_select ) {
|
||||
on_select(*this);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#if 0
|
||||
switch(event.type) {
|
||||
case TouchEvent::Type::Start:
|
||||
flags.highlighted = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
|
||||
case TouchEvent::Type::Move:
|
||||
{
|
||||
const bool new_highlighted = screen_rect().contains(event.point);
|
||||
if( flags.highlighted != new_highlighted ) {
|
||||
flags.highlighted = new_highlighted;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case TouchEvent::Type::End:
|
||||
if( flags.highlighted ) {
|
||||
flags.highlighted = false;
|
||||
set_dirty();
|
||||
if( on_select ) {
|
||||
on_select(*this);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* OptionsField **********************************************************/
|
||||
|
||||
size_t OptionsField::selected_index() const {
|
||||
return selected_index_;
|
||||
}
|
||||
|
||||
void OptionsField::set_selected_index(const size_t new_index) {
|
||||
if( new_index < options.size() ) {
|
||||
if( new_index != selected_index() ) {
|
||||
selected_index_ = new_index;
|
||||
if( on_change ) {
|
||||
on_change(selected_index(), options[selected_index()].second);
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsField::set_by_value(value_t v) {
|
||||
size_t new_index { 0 };
|
||||
for(const auto& option : options) {
|
||||
if( option.second >= v ) {
|
||||
set_selected_index(new_index);
|
||||
break;
|
||||
}
|
||||
new_index++;
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsField::paint(Painter& painter) {
|
||||
const auto paint_style = has_focus() ? style().invert() : style();
|
||||
|
||||
if( selected_index() < options.size() ) {
|
||||
const auto text = options[selected_index()].first;
|
||||
painter.draw_string(
|
||||
screen_pos(),
|
||||
paint_style,
|
||||
text
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool OptionsField::on_encoder(const EncoderEvent delta) {
|
||||
set_selected_index(selected_index() + delta);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionsField::on_touch(const TouchEvent event) {
|
||||
if( event.type == TouchEvent::Type::Start ) {
|
||||
focus();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* NumberField ***********************************************************/
|
||||
|
||||
int32_t NumberField::value() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
void NumberField::set_value(int32_t new_value) {
|
||||
new_value = clip_value(new_value);
|
||||
|
||||
if( new_value != value() ) {
|
||||
value_ = new_value;
|
||||
if( on_change ) {
|
||||
on_change(value_);
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void NumberField::paint(Painter& painter) {
|
||||
const auto text = to_string_dec_int(value_, length_, fill_char);
|
||||
|
||||
const auto paint_style = has_focus() ? style().invert() : style();
|
||||
|
||||
painter.draw_string(
|
||||
screen_pos(),
|
||||
paint_style,
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
bool NumberField::on_encoder(const EncoderEvent delta) {
|
||||
set_value(value() + (delta * step));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NumberField::on_touch(const TouchEvent event) {
|
||||
if( event.type == TouchEvent::Type::Start ) {
|
||||
focus();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t NumberField::clip_value(int32_t value) {
|
||||
if( value > range.second ) {
|
||||
value = range.second;
|
||||
}
|
||||
if( value < range.first ) {
|
||||
value = range.first;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
Loading…
Add table
Add a link
Reference in a new issue