diff --git a/examples/play-glfw/MainWindow.cpp b/examples/play-glfw/MainWindow.cpp index a139d84c6..ac840a15e 100644 --- a/examples/play-glfw/MainWindow.cpp +++ b/examples/play-glfw/MainWindow.cpp @@ -9,9 +9,9 @@ #include #include +#include +#include #include -#include -#include #include #include #include @@ -31,7 +31,7 @@ namespace tl struct MainWindow::Private { std::shared_ptr timeUnitsModel; - std::shared_ptr speedModel; + std::shared_ptr speedModel; timelineui::ItemOptions itemOptions; std::shared_ptr timelineViewport; @@ -39,7 +39,7 @@ namespace tl std::shared_ptr playbackButtonGroup; std::shared_ptr frameButtonGroup; std::shared_ptr currentTimeEdit; - std::shared_ptr speedEdit; + std::shared_ptr speedEdit; std::shared_ptr durationLabel; std::shared_ptr timeUnitsComboBox; std::shared_ptr splitter; @@ -47,7 +47,7 @@ namespace tl std::shared_ptr > timeUnitsObserver; std::shared_ptr > speedObserver; - std::shared_ptr > speedObserver2; + std::shared_ptr > speedObserver2; std::shared_ptr > playbackObserver; std::shared_ptr > currentTimeObserver; }; @@ -62,8 +62,8 @@ namespace tl setBackgroundRole(ui::ColorRole::Window); p.timeUnitsModel = timeline::TimeUnitsModel::create(context); - p.speedModel = ui::FloatModel::create(context); - p.speedModel->setRange(math::FloatRange(0.F, 1000.F)); + p.speedModel = ui::DoubleModel::create(context); + p.speedModel->setRange(math::DoubleRange(0.0, 1000.0)); p.speedModel->setStep(1.F); p.speedModel->setLargeStep(10.F); @@ -103,8 +103,8 @@ namespace tl p.currentTimeEdit = ui::TimeEdit::create(p.timeUnitsModel, context); auto currentTimeIncButtons = ui::IncButtons::create(context); - p.speedEdit = ui::FloatEdit::create(p.speedModel, context); - auto speedIncButtons = ui::FloatIncButtons::create(p.speedModel, context); + p.speedEdit = ui::DoubleEdit::create(p.speedModel, context); + auto speedIncButtons = ui::DoubleIncButtons::create(p.speedModel, context); p.durationLabel = ui::TimeLabel::create(p.timeUnitsModel, context); p.durationLabel->setValue(player->getTimeRange().duration()); @@ -181,9 +181,9 @@ namespace tl { _p->speedModel->setValue(value); }); - p.speedObserver2 = observer::ValueObserver::create( + p.speedObserver2 = observer::ValueObserver::create( p.speedModel->observeValue(), - [player](float value) + [player](double value) { player->setSpeed(value); }); diff --git a/lib/tlCore/Range.cpp b/lib/tlCore/Range.cpp index 9ae636318..856915581 100644 --- a/lib/tlCore/Range.cpp +++ b/lib/tlCore/Range.cpp @@ -28,6 +28,11 @@ namespace tl json = { value.getMin(), value.getMax() }; } + void to_json(nlohmann::json& json, const DoubleRange& value) + { + json = { value.getMin(), value.getMax() }; + } + void from_json(const nlohmann::json& json, IntRange& value) { int min = 0; @@ -55,6 +60,15 @@ namespace tl value = FloatRange(min, max); } + void from_json(const nlohmann::json& json, DoubleRange& value) + { + double min = 0.0; + double max = 0.0; + json.at(0).get_to(min); + json.at(1).get_to(max); + value = DoubleRange(min, max); + } + std::ostream& operator << (std::ostream& os, const IntRange& value) { os << value.getMin() << "-" << value.getMax(); @@ -73,6 +87,12 @@ namespace tl return os; } + std::ostream& operator << (std::ostream& os, const DoubleRange& value) + { + os << value.getMin() << "-" << value.getMax(); + return os; + } + std::istream& operator >> (std::istream& is, IntRange& value) { std::string s; @@ -141,5 +161,28 @@ namespace tl value = FloatRange(min, max); return is; } + + std::istream& operator >> (std::istream& is, DoubleRange& value) + { + std::string s; + is >> s; + auto split = string::split(s, '-'); + if (split.size() != 2) + { + throw error::ParseError(); + } + double min = 0.0; + double max = 0.0; + { + std::stringstream ss(split[0]); + ss >> min; + } + { + std::stringstream ss(split[1]); + ss >> max; + } + value = DoubleRange(min, max); + return is; + } } } \ No newline at end of file diff --git a/lib/tlCore/Range.h b/lib/tlCore/Range.h index a65b2ef96..2a3aae67c 100644 --- a/lib/tlCore/Range.h +++ b/lib/tlCore/Range.h @@ -61,21 +61,28 @@ namespace tl //! This typedef provides a floating point range. typedef Range FloatRange; + //! This typedef provides a double precision floating point range. + typedef Range DoubleRange; + void to_json(nlohmann::json&, const IntRange&); void to_json(nlohmann::json&, const SizeTRange&); void to_json(nlohmann::json&, const FloatRange&); + void to_json(nlohmann::json&, const DoubleRange&); void from_json(const nlohmann::json&, IntRange&); void from_json(const nlohmann::json&, SizeTRange&); void from_json(const nlohmann::json&, FloatRange&); + void from_json(const nlohmann::json&, DoubleRange&); std::ostream& operator << (std::ostream&, const IntRange&); std::ostream& operator << (std::ostream&, const SizeTRange&); std::ostream& operator << (std::ostream&, const FloatRange&); + std::ostream& operator << (std::ostream&, const DoubleRange&); std::istream& operator >> (std::istream&, IntRange&); std::istream& operator >> (std::istream&, SizeTRange&); std::istream& operator >> (std::istream&, FloatRange&); + std::istream& operator >> (std::istream&, DoubleRange&); } } diff --git a/lib/tlUI/CMakeLists.txt b/lib/tlUI/CMakeLists.txt index 14def26ce..18cf7181a 100644 --- a/lib/tlUI/CMakeLists.txt +++ b/lib/tlUI/CMakeLists.txt @@ -1,6 +1,8 @@ set(HEADERS ButtonGroup.h ComboBox.h + DoubleEdit.h + DoubleModel.h DrawUtil.h Event.h EventLoop.h @@ -43,6 +45,8 @@ set(HEADERS_PRIVATE) set(SOURCE ButtonGroup.cpp ComboBox.cpp + DoubleEdit.cpp + DoubleModel.cpp DrawUtil.cpp Event.cpp EventLoop.cpp diff --git a/lib/tlUI/DoubleEdit.cpp b/lib/tlUI/DoubleEdit.cpp new file mode 100644 index 000000000..e51b92342 --- /dev/null +++ b/lib/tlUI/DoubleEdit.cpp @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2021-2023 Darby Johnston +// All rights reserved. + +#include + +#include +#include +#include + +#include + +namespace tl +{ + namespace ui + { + struct DoubleEdit::Private + { + std::shared_ptr model; + std::shared_ptr lineEdit; + int digits = 3; + int precision = 2; + + struct SizeData + { + int margin = 0; + }; + SizeData size; + + std::shared_ptr > valueObserver; + std::shared_ptr > rangeObserver; + }; + + void DoubleEdit::_init( + const std::shared_ptr& model, + const std::shared_ptr& context, + const std::shared_ptr& parent) + { + IWidget::_init("tl::ui::DoubleEdit", context, parent); + TLRENDER_P(); + + p.lineEdit = LineEdit::create(context, shared_from_this()); + p.lineEdit->setFontRole(FontRole::Mono); + + p.model = model; + if (!p.model) + { + p.model = DoubleModel::create(context); + } + + p.lineEdit->setTextCallback( + [this](const std::string& value) + { + _p->model->setValue(std::atof(value.c_str())); + _textUpdate(); + }); + p.lineEdit->setFocusCallback( + [this](bool value) + { + if (!value) + { + _textUpdate(); + } + }); + + p.valueObserver = observer::ValueObserver::create( + p.model->observeValue(), + [this](double) + { + _textUpdate(); + }); + + p.rangeObserver = observer::ValueObserver::create( + p.model->observeRange(), + [this](const math::DoubleRange&) + { + _textUpdate(); + }); + + _textUpdate(); + } + + DoubleEdit::DoubleEdit() : + _p(new Private) + {} + + DoubleEdit::~DoubleEdit() + {} + + std::shared_ptr DoubleEdit::create( + const std::shared_ptr& model, + const std::shared_ptr& context, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new DoubleEdit); + out->_init(model, context, parent); + return out; + } + + const std::shared_ptr& DoubleEdit::getModel() const + { + return _p->model; + } + + void DoubleEdit::setDigits(int value) + { + TLRENDER_P(); + if (value == p.digits) + return; + p.digits = value; + _textUpdate(); + } + + void DoubleEdit::setPrecision(int value) + { + TLRENDER_P(); + if (value == p.precision) + return; + p.precision = value; + _textUpdate(); + } + + void DoubleEdit::setFontRole(FontRole value) + { + _p->lineEdit->setFontRole(value); + } + + void DoubleEdit::setGeometry(const math::BBox2i& value) + { + IWidget::setGeometry(value); + _p->lineEdit->setGeometry(value); + } + + void DoubleEdit::sizeHintEvent(const SizeHintEvent& event) + { + IWidget::sizeHintEvent(event); + _sizeHint = _p->lineEdit->getSizeHint(); + } + + void DoubleEdit::keyPressEvent(KeyEvent& event) + { + TLRENDER_P(); + if (isEnabled() && p.model) + { + switch (event.key) + { + case Key::Down: + event.accept = true; + p.model->decrementStep(); + break; + case Key::Up: + event.accept = true; + p.model->incrementStep(); + break; + case Key::PageUp: + event.accept = true; + p.model->incrementLargeStep(); + break; + case Key::PageDown: + event.accept = true; + p.model->decrementLargeStep(); + break; + } + } + } + + void DoubleEdit::keyReleaseEvent(KeyEvent& event) + { + event.accept = true; + } + + void DoubleEdit::_textUpdate() + { + TLRENDER_P(); + std::string text; + std::string format; + if (p.model) + { + text = string::Format("{0}").arg(p.model->getValue(), p.precision); + format = string::Format("{0}").arg(0, p.digits + 1 + p.precision); + } + p.lineEdit->setText(text); + p.lineEdit->setFormat(format); + } + } +} diff --git a/lib/tlUI/DoubleEdit.h b/lib/tlUI/DoubleEdit.h new file mode 100644 index 000000000..d89680664 --- /dev/null +++ b/lib/tlUI/DoubleEdit.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2021-2023 Darby Johnston +// All rights reserved. + +#pragma once + +#include + +namespace tl +{ + namespace ui + { + class DoubleModel; + + //! Double precision floating point number editor. + class DoubleEdit : public IWidget + { + TLRENDER_NON_COPYABLE(DoubleEdit); + + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + DoubleEdit(); + + public: + ~DoubleEdit() override; + + //! Create a new widget. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + //! Get the model. + const std::shared_ptr& getModel() const; + + //! Set the number of digits to display. + void setDigits(int); + + //! Set the display precision. + void setPrecision(int); + + //! Set the font role. + void setFontRole(FontRole); + + void setGeometry(const math::BBox2i&) override; + void sizeHintEvent(const SizeHintEvent&) override; + void keyPressEvent(KeyEvent&) override; + void keyReleaseEvent(KeyEvent&) override; + + private: + void _textUpdate(); + + TLRENDER_PRIVATE(); + }; + } +} diff --git a/lib/tlUI/DoubleModel.cpp b/lib/tlUI/DoubleModel.cpp new file mode 100644 index 000000000..89f7c23c7 --- /dev/null +++ b/lib/tlUI/DoubleModel.cpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2021-2023 Darby Johnston +// All rights reserved. + +#include + +#include + +namespace tl +{ + namespace ui + { + struct DoubleModel::Private + { + std::shared_ptr > value; + std::shared_ptr > range; + double step = 0.1; + double largeStep = 1.0; + }; + + void DoubleModel::_init(const std::shared_ptr&) + { + TLRENDER_P(); + p.value = observer::Value::create(0.0); + p.range = observer::Value::create(math::DoubleRange(0.0, 1.0)); + } + + DoubleModel::DoubleModel() : + _p(new Private) + {} + + DoubleModel::~DoubleModel() + {} + + std::shared_ptr DoubleModel::create( + const std::shared_ptr& context) + { + auto out = std::shared_ptr(new DoubleModel); + out->_init(context); + return out; + } + + double DoubleModel::getValue() const + { + return _p->value->get(); + } + + void DoubleModel::setValue(double value) + { + TLRENDER_P(); + const math::DoubleRange& range = p.range->get(); + const double tmp = math::clamp(value, range.getMin(), range.getMax()); + _p->value->setIfChanged(tmp); + } + + std::shared_ptr > DoubleModel::observeValue() const + { + return _p->value; + } + + const math::DoubleRange& DoubleModel::getRange() const + { + return _p->range->get(); + } + + void DoubleModel::setRange(const math::DoubleRange& range) + { + TLRENDER_P(); + if (p.range->setIfChanged(range)) + { + setValue(p.value->get()); + } + } + + std::shared_ptr > DoubleModel::observeRange() const + { + return _p->range; + } + + double DoubleModel::getStep() const + { + return _p->step; + } + + void DoubleModel::setStep(double value) + { + _p->step = value; + } + + void DoubleModel::incrementStep() + { + TLRENDER_P(); + setValue(p.value->get() + p.step); + } + + void DoubleModel::decrementStep() + { + TLRENDER_P(); + setValue(p.value->get() - p.step); + } + + double DoubleModel::getLargeStep() const + { + return _p->largeStep; + } + + void DoubleModel::setLargeStep(double value) + { + _p->largeStep = value; + } + + void DoubleModel::incrementLargeStep() + { + TLRENDER_P(); + setValue(p.value->get() + p.largeStep); + } + + void DoubleModel::decrementLargeStep() + { + TLRENDER_P(); + setValue(p.value->get() - p.largeStep); + } + } +} diff --git a/lib/tlUI/DoubleModel.h b/lib/tlUI/DoubleModel.h new file mode 100644 index 000000000..7dcfdeba2 --- /dev/null +++ b/lib/tlUI/DoubleModel.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2021-2023 Darby Johnston +// All rights reserved. + +#pragma once + +#include +#include +#include + +namespace tl +{ + namespace ui + { + //! Double precision floating point value model. + class DoubleModel : public std::enable_shared_from_this + { + TLRENDER_NON_COPYABLE(DoubleModel); + + void _init(const std::shared_ptr&); + + protected: + DoubleModel(); + + public: + ~DoubleModel(); + + //! Create a new model. + static std::shared_ptr create( + const std::shared_ptr&); + + //! \name Value + ///@{ + + double getValue() const; + + void setValue(double); + + std::shared_ptr > observeValue() const; + + ///@} + + //! \name Range + ///@{ + + const math::DoubleRange& getRange() const; + + void setRange(const math::DoubleRange&); + + std::shared_ptr > observeRange() const; + + ///@} + + //! \name Increment + ///@{ + + double getStep() const; + + void setStep(double); + + void incrementStep(); + void decrementStep(); + + double getLargeStep() const; + + void setLargeStep(double); + + void incrementLargeStep(); + void decrementLargeStep(); + + ///@} + + private: + TLRENDER_PRIVATE(); + }; + } +} diff --git a/lib/tlUI/FloatEdit.h b/lib/tlUI/FloatEdit.h index de996d34e..1fd0a88ab 100644 --- a/lib/tlUI/FloatEdit.h +++ b/lib/tlUI/FloatEdit.h @@ -34,7 +34,7 @@ namespace tl const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - //! Get the floating point model. + //! Get the model. const std::shared_ptr& getModel() const; //! Set the number of digits to display. diff --git a/lib/tlUI/FloatSlider.h b/lib/tlUI/FloatSlider.h index 7293f6c19..31425730d 100644 --- a/lib/tlUI/FloatSlider.h +++ b/lib/tlUI/FloatSlider.h @@ -34,7 +34,7 @@ namespace tl const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - //! Get the floating point model. + //! Get the model. const std::shared_ptr& getModel() const; void setVisible(bool) override; diff --git a/lib/tlUI/IncButtons.cpp b/lib/tlUI/IncButtons.cpp index 12b392566..259be6b78 100644 --- a/lib/tlUI/IncButtons.cpp +++ b/lib/tlUI/IncButtons.cpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -325,5 +326,83 @@ namespace tl _incButton->setEnabled(value < range.getMax()); _decButton->setEnabled(value > range.getMin()); } + + struct DoubleIncButtons::Private + { + std::shared_ptr model; + std::shared_ptr > valueObserver; + std::shared_ptr > rangeObserver; + }; + + void DoubleIncButtons::_init( + const std::shared_ptr& model, + const std::shared_ptr& context, + const std::shared_ptr& parent) + { + IncButtons::_init(context, parent); + setName("tl::ui::DoubleIncButtons"); + TLRENDER_P(); + + p.model = model; + + _modelUpdate(); + + _incButton->setClickedCallback( + [this] + { + _p->model->incrementStep(); + }); + + _decButton->setClickedCallback( + [this] + { + _p->model->decrementStep(); + }); + + p.valueObserver = observer::ValueObserver::create( + p.model->observeValue(), + [this](double) + { + _modelUpdate(); + }); + + p.rangeObserver = observer::ValueObserver::create( + p.model->observeRange(), + [this](const math::DoubleRange&) + { + _modelUpdate(); + }); + } + + DoubleIncButtons::DoubleIncButtons() : + _p(new Private) + {} + + DoubleIncButtons::~DoubleIncButtons() + {} + + std::shared_ptr DoubleIncButtons::create( + const std::shared_ptr& model, + const std::shared_ptr& context, + const std::shared_ptr& parent) + { + auto out = std::shared_ptr(new DoubleIncButtons); + out->_init(model, context, parent); + return out; + } + + const std::shared_ptr& DoubleIncButtons::getModel() const + { + return _p->model; + } + + void DoubleIncButtons::_modelUpdate() + { + TLRENDER_P(); + const double value = p.model->getValue(); + const math::DoubleRange& range = p.model->getRange(); + _incButton->setEnabled(value < range.getMax()); + _decButton->setEnabled(value > range.getMin()); + } } } diff --git a/lib/tlUI/IncButtons.h b/lib/tlUI/IncButtons.h index 6329dde9c..39f0bc0bf 100644 --- a/lib/tlUI/IncButtons.h +++ b/lib/tlUI/IncButtons.h @@ -10,6 +10,7 @@ namespace tl { namespace ui { + class DoubleModel; class FloatModel; class IntModel; @@ -57,7 +58,7 @@ namespace tl public: ~IncButtons() override; - //! Create new increment buttons. + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr& parent = nullptr); @@ -92,13 +93,13 @@ namespace tl public: ~IntIncButtons() override; - //! Create new increment buttons. + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - //! Get the integer model. + //! Get the model. const std::shared_ptr& getModel() const; private: @@ -123,13 +124,13 @@ namespace tl public: ~FloatIncButtons() override; - //! Create new increment buttons. + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - //! Get the floating point model. + //! Get the model. const std::shared_ptr& getModel() const; private: @@ -137,5 +138,37 @@ namespace tl TLRENDER_PRIVATE(); }; + + //! Buttons for incrementing and decrementing a double precision + //! floating point value. + class DoubleIncButtons : public IncButtons + { + TLRENDER_NON_COPYABLE(DoubleIncButtons); + + protected: + void _init( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + DoubleIncButtons(); + + public: + ~DoubleIncButtons() override; + + //! Create a new widget. + static std::shared_ptr create( + const std::shared_ptr&, + const std::shared_ptr&, + const std::shared_ptr& parent = nullptr); + + //! Get the model. + const std::shared_ptr& getModel() const; + + private: + void _modelUpdate(); + + TLRENDER_PRIVATE(); + }; } } diff --git a/lib/tlUI/IntEdit.h b/lib/tlUI/IntEdit.h index 3178e3e6c..358e737c0 100644 --- a/lib/tlUI/IntEdit.h +++ b/lib/tlUI/IntEdit.h @@ -34,7 +34,7 @@ namespace tl const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - //! Get the integer model. + //! Get the model. const std::shared_ptr& getModel() const; //! Set the number of digits to display. diff --git a/lib/tlUI/IntSlider.h b/lib/tlUI/IntSlider.h index 4ed26c03c..46df3a70a 100644 --- a/lib/tlUI/IntSlider.h +++ b/lib/tlUI/IntSlider.h @@ -34,7 +34,7 @@ namespace tl const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - //! Get the integer model. + //! Get the model. const std::shared_ptr& getModel() const; void setVisible(bool) override;