Simple drawing example.

This commit is contained in:
James Grogan 2022-12-05 10:59:45 +00:00
parent d7fe11913f
commit f0091f9e04
27 changed files with 450 additions and 68 deletions

View file

@ -13,12 +13,13 @@ list(APPEND client_HEADERS
image_editor/ImageViewWidget.h
canvas/CanvasView.h
canvas/CanvasController.h
canvas/CanvasDrawingArea.h
canvas/CanvasCommandSelectorView.h
mesh_viewer/MeshViewerView.h
mesh_viewer/MeshViewerController.h
mesh_viewer/MeshViewerCanvas.h
web_client/WebClientView.h)
list(APPEND client_LIB_INCLUDES
text_editor/TextEditorView.cpp
text_editor/TextEditorModel.cpp
@ -32,6 +33,8 @@ list(APPEND client_LIB_INCLUDES
mesh_viewer/MeshViewerCanvas.cpp
canvas/CanvasView.cpp
canvas/CanvasController.cpp
canvas/CanvasDrawingArea.cpp
canvas/CanvasCommandSelectorView.cpp
web_client/WebClientView.cpp
NotesTk.cpp)

View file

@ -0,0 +1,42 @@
#include "CanvasCommandSelectorView.h"
#include "HorizontalSpacer.h"
#include "Button.h"
#include "Theme.h"
CanvasCommandSelectorView::CanvasCommandSelectorView()
: Widget()
{
auto circle_button = Button::Create();
auto on_circle_click = [this](Widget* self){
onCommandSelected(CanvasDrawCommand::CIRCLE);
};
circle_button->setLabel("Circle");
circle_button->setBackgroundColor(Theme::getButtonPrimaryBackground());
circle_button->setMargin(2);
circle_button->setOnClickFunction(on_circle_click);
auto on_line_click = [this](Widget* self){
onCommandSelected(CanvasDrawCommand::LINE);
};
auto line_button = Button::Create();
line_button->setLabel("Line");
line_button->setBackgroundColor(Theme::getButtonPrimaryBackground());
line_button->setMargin(2);
line_button->setOnClickFunction(on_line_click);
auto hspacer = HorizontalSpacer::Create();
hspacer->addWidgetWithScale(std::move(circle_button), 1);
hspacer->addWidgetWithScale(std::move(line_button), 1);
addWidget(std::move(hspacer));
}
void CanvasCommandSelectorView::onCommandSelected(CanvasDrawCommand command)
{
if(mCommandSelectedCallback)
{
mCommandSelectedCallback(command);
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "Widget.h"
#include "CanvasElements.h"
#include <functional>
class CanvasCommandSelectorView : public Widget
{
public:
using onCommandSelectedFunc = std::function<void(CanvasDrawCommand command)>;
CanvasCommandSelectorView();
void setCommandSelectedCallback(onCommandSelectedFunc func)
{
mCommandSelectedCallback = func;
}
void onCommandSelected(CanvasDrawCommand command);
private:
onCommandSelectedFunc mCommandSelectedCallback{nullptr};
};

View file

@ -0,0 +1,54 @@
#include "CanvasDrawingArea.h"
#include "MouseEvent.h"
#include "GeometryNode.h"
#include "TransformNode.h"
#include "CircleNode.h"
#include <iostream>
CanvasDrawingArea::~CanvasDrawingArea()
{
}
void CanvasDrawingArea::addShapeAt(unsigned x, unsigned y)
{
if (mActiveDrawingCommand == CanvasDrawCommand::CIRCLE)
{
auto circle = std::make_unique<CircleNode>(DiscretePoint(x, y), 5);
circle->setFillColor(Color(255, 0, 0));
circle->setName("CanvasDrawingArea_CircleNode");
mRootNode->addChild(circle.get());
mSceneNodes.push_back(std::move(circle));
mContentDirty = true;
}
}
bool CanvasDrawingArea::isDirty() const
{
return Widget::isDirty() || mContentDirty;
}
void CanvasDrawingArea::doPaint(const PaintEvent* event)
{
mContentDirty = false;
}
void CanvasDrawingArea::onMyMouseEvent(const MouseEvent* event)
{
if(event->GetAction() == MouseEvent::Action::Pressed)
{
}
else if(event->GetAction() == MouseEvent::Action::Released)
{
auto client_loc = event->GetClientLocation();
auto screen_loc = event->GetScreenLocation();
addShapeAt(client_loc.GetX(), client_loc.GetY());
}
}

View file

@ -0,0 +1,29 @@
#pragma once
#include "Widget.h"
#include "CanvasElements.h"
class GeometryNode;
class CanvasDrawingArea : public Widget
{
public:
~CanvasDrawingArea();
void setActiveDrawingCommand(CanvasDrawCommand command)
{
mActiveDrawingCommand = command;
}
private:
bool isDirty() const override;
void doPaint(const PaintEvent* event) override;
void onMyMouseEvent(const MouseEvent* event) override;
void addShapeAt(unsigned x, unsigned y);
CanvasDrawCommand mActiveDrawingCommand{CanvasDrawCommand::LINE};
std::vector<std::unique_ptr<GeometryNode> > mSceneNodes;
bool mContentDirty{false};
};

View file

@ -0,0 +1,7 @@
#pragma once
enum class CanvasDrawCommand
{
CIRCLE,
LINE
};

View file

@ -4,15 +4,16 @@
#include "VerticalSpacer.h"
#include "CanvasController.h"
#include "CanvasDrawingArea.h"
#include "CanvasCommandSelectorView.h"
#include "Theme.h"
#include "TextNode.h"
#include "GeometryNode.h"
#include "Label.h"
#include "Button.h"
#include <iostream>
CanvasView::CanvasView()
: mController(CanvasController::Create())
{
@ -36,11 +37,28 @@ void CanvasView::initialize()
label->setBackgroundColor(Theme::getBannerBackground());
label->setMargin(1);
auto controls = std::make_unique<CanvasCommandSelectorView>();
controls->setBackgroundColor(Theme::getBannerBackground());
controls->setMargin(1);
auto on_draw_command_changed = [this](CanvasDrawCommand command){
onDrawCommandChanged(command);
};
controls->setCommandSelectedCallback(on_draw_command_changed);
auto drawing_area = std::make_unique<CanvasDrawingArea>();
drawing_area->setBackgroundColor(Theme::getBackgroundPrimary());
drawing_area->setMargin(1);
mDrawingArea = drawing_area.get();
auto control_draw_vspacer = VerticalSpacer::Create();
control_draw_vspacer->addWidgetWithScale(std::move(controls), 1);
control_draw_vspacer->addWidgetWithScale(std::move(drawing_area), 10);
auto hSpacer = HorizontalSpacer::Create();
hSpacer->addWidgetWithScale(std::move(label), 1);
//hSpacer->addWidgetWithScale(std::move(textBox), 14);
hSpacer->addWidgetWithScale(std::move(control_draw_vspacer), 10);
auto cache_button_spacer = initializeCacheButtons();
hSpacer->addWidgetWithScale(std::move(cache_button_spacer), 1);
@ -48,6 +66,11 @@ void CanvasView::initialize()
addWidget(std::move(hSpacer));
}
void CanvasView::onDrawCommandChanged(CanvasDrawCommand command)
{
mDrawingArea->setActiveDrawingCommand(command);
}
std::unique_ptr<Widget> CanvasView::initializeCacheButtons()
{
auto saveButton = Button::Create();
@ -66,10 +89,9 @@ std::unique_ptr<Widget> CanvasView::initializeCacheButtons()
loadButton->setMargin(2);
auto buttonSpacer = VerticalSpacer::Create();
buttonSpacer->AddWidgetWithScale(std::move(saveButton), 1);
buttonSpacer->AddWidgetWithScale(std::move(clearButton), 1);
buttonSpacer->AddWidgetWithScale(std::move(loadButton), 1);
buttonSpacer->addWidgetWithScale(std::move(saveButton), 1);
buttonSpacer->addWidgetWithScale(std::move(clearButton), 1);
buttonSpacer->addWidgetWithScale(std::move(loadButton), 1);
return buttonSpacer;
}

View file

@ -1,8 +1,10 @@
#pragma once
#include "Widget.h"
#include "CanvasElements.h"
class CanvasController;
class CanvasDrawingArea;
class CanvasView : public Widget
{
@ -14,13 +16,14 @@ public:
static std::unique_ptr<CanvasView> Create();
private:
void onDrawCommandChanged(CanvasDrawCommand command);
void initialize();
std::unique_ptr<Widget> initializeCacheButtons();
//std::unique_ptr<Widget> initializeCacheButtons();
std::unique_ptr<CanvasController> mController;
CanvasDrawingArea* mDrawingArea{nullptr};
};
using CanvasViewPtr = std::unique_ptr<CanvasView>;

View file

@ -72,9 +72,9 @@ void TextEditorView::initialize()
loadButton->setOnClickFunction(onLoad);
auto buttonSpacer = VerticalSpacer::Create();
buttonSpacer->AddWidgetWithScale(std::move(saveButton), 1);
buttonSpacer->AddWidgetWithScale(std::move(clearButton), 1);
buttonSpacer->AddWidgetWithScale(std::move(loadButton), 1);
buttonSpacer->addWidgetWithScale(std::move(saveButton), 1);
buttonSpacer->addWidgetWithScale(std::move(clearButton), 1);
buttonSpacer->addWidgetWithScale(std::move(loadButton), 1);
auto hSpacer = HorizontalSpacer::Create();
hSpacer->addWidgetWithScale(std::move(label), 1);

View file

@ -11,7 +11,6 @@
class TextEditorView : public Widget
{
public:
TextEditorView();
static std::unique_ptr<TextEditorView> Create();

View file

@ -17,8 +17,8 @@ TabbedPanelWidget::TabbedPanelWidget()
nav->setSize({0, 0, 0, 0, 200, 0});
auto vertSpacer = VerticalSpacer::Create();
vertSpacer->AddWidgetWithScale(std::move(nav), 1);
vertSpacer->AddWidgetWithScale(std::move(stack), 4);
vertSpacer->addWidgetWithScale(std::move(nav), 1);
vertSpacer->addWidgetWithScale(std::move(stack), 4);
addWidget(std::move(vertSpacer));
}

View file

@ -12,63 +12,63 @@
void SharedMemory::allocate(const std::string& namePrefix, std::size_t size)
{
createFile(namePrefix);
createFile(namePrefix);
if (!mIsValid)
{
return;
}
if (!mIsValid)
{
return;
}
#ifdef __linux__
int ret{-1};
do {
ret = ftruncate(mFileDescriptor, size);
} while (ret < 0 && errno == EINTR);
int ret{-1};
do {
ret = ftruncate(mFileDescriptor, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
{
close(mFileDescriptor);
mIsValid = false;
}
if (ret < 0)
{
close(mFileDescriptor);
mIsValid = false;
}
#endif
}
int SharedMemory::getFileDescriptor() const
{
return mFileDescriptor;
return mFileDescriptor;
}
bool SharedMemory::isValid() const
{
return mIsValid;
return mIsValid;
}
void SharedMemory::createFile(const std::string& namePrefix)
{
#ifdef __linux__
unsigned retries = 100;
do {
const auto name = getRandomName(namePrefix);
--retries;
unsigned retries = 100;
do {
const auto name = getRandomName(namePrefix);
--retries;
const int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
{
shm_unlink(name.c_str());
mFileDescriptor = fd;
mIsValid = true;
break;
}
} while (retries > 0 && errno == EEXIST);
const int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
{
shm_unlink(name.c_str());
mFileDescriptor = fd;
mIsValid = true;
break;
}
} while (retries > 0 && errno == EEXIST);
#endif
}
std::string SharedMemory::getRandomName(const std::string& namePrefix) const
{
std::string randomSuffix;
for (const auto entry : RandomUtils::getRandomVecUnsigned(6))
{
randomSuffix += std::to_string(entry);
}
return namePrefix + randomSuffix;
std::string randomSuffix;
for (const auto entry : RandomUtils::getRandomVecUnsigned(6))
{
randomSuffix += std::to_string(entry);
}
return namePrefix + randomSuffix;
}

View file

@ -5,18 +5,18 @@
class SharedMemory
{
public:
void allocate(const std::string& namePrefix, std::size_t size);
void allocate(const std::string& namePrefix, std::size_t size);
int getFileDescriptor() const;
int getFileDescriptor() const;
bool isValid() const;
bool isValid() const;
private:
void createFile(const std::string& namePrefix);
void createFile(const std::string& namePrefix);
std::string getRandomName(const std::string& namePrefix) const;
std::string getRandomName(const std::string& namePrefix) const;
int mFileDescriptor{0};
bool mIsValid{false};
int mFileDescriptor{0};
bool mIsValid{false};
};

View file

@ -110,6 +110,7 @@ void OpenGlMeshPainter::paint(SceneModel* model, DrawingContext* context)
auto y = vertices[idx]*transform.getScaleY() + transform.getLocation().getY();
vertices[idx] = 1.0 - 2*y/height;
}
}
std::vector<unsigned> indices;

View file

@ -32,6 +32,74 @@ std::unique_ptr<TriMesh> MeshPrimitives::buildRectangleAsTriMesh()
return MeshBuilder::buildTriMesh(locations, edge_ids, face_ids);
}
std::unique_ptr<TriMesh> MeshPrimitives::buildCircleAsTriMesh(unsigned numSegments)
{
VecPoints locations(numSegments + 1);
locations[0] = {0, 0};
const double delta_theta = (2.0*M_PI)/double(numSegments);
double theta = 0.0;
for(unsigned idx=1; idx<=numSegments; idx++)
{
const double x = sin(theta);
const double y = cos(theta);
locations[idx] = {x, y};
theta += delta_theta;
}
EdgeIds edge_ids(2*numSegments);
for(unsigned idx=0; idx<numSegments; idx++)
{
edge_ids[idx] = {0, idx+1};
auto wrap_node = idx + 2;
if (wrap_node > numSegments)
{
wrap_node = 1;
}
edge_ids[idx + numSegments] = {idx + 1, wrap_node};
}
FaceIds face_ids(numSegments);
for(unsigned idx=0; idx<numSegments; idx++)
{
auto top_edge_inner = idx + 1;
if (top_edge_inner == numSegments)
{
top_edge_inner = 0;
}
const auto outer_edge = idx + numSegments;
face_ids[idx] = {idx, outer_edge, top_edge_inner};
}
return MeshBuilder::buildTriMesh(locations, edge_ids, face_ids);
}
std::unique_ptr<LineMesh> MeshPrimitives::buildCircleAsLineMesh(unsigned numSegments)
{
VecPoints locations(numSegments);
const double delta_theta = (2.0*M_PI)/double(numSegments);
double theta = 0.0;
for(unsigned idx=0; idx<numSegments; idx++)
{
const double x = sin(theta);
const double y = cos(theta);
locations[idx] = {x, y};
}
EdgeIds edge_ids(2*numSegments);
for(unsigned idx=0; idx<numSegments; idx++)
{
auto top_node = idx + 1;
if (top_node == numSegments)
{
top_node = 0;
}
edge_ids[idx] = {idx, top_node};
}
return MeshBuilder::buildLineMesh(locations, edge_ids);
}
std::unique_ptr<TriMesh> MeshPrimitives::buildRoundedRectangleAsTriMesh(double radius, double aspect_ratio, unsigned num_segments)
{
unsigned num_fans = 4;

View file

@ -6,6 +6,9 @@
class MeshPrimitives
{
public:
static std::unique_ptr<TriMesh> buildCircleAsTriMesh(unsigned numSegments = 24);
static std::unique_ptr<LineMesh> buildCircleAsLineMesh(unsigned numSegments = 24);
static std::unique_ptr<TriMesh> buildRectangleAsTriMesh();

View file

@ -17,10 +17,10 @@ std::unique_ptr<VerticalSpacer> VerticalSpacer::Create()
void VerticalSpacer::addWidget(WidgetUPtr widget)
{
AddWidgetWithScale(std::move(widget), 1.0);
addWidgetWithScale(std::move(widget), 1.0);
}
void VerticalSpacer::AddWidgetWithScale(WidgetUPtr widget, double scale)
void VerticalSpacer::addWidgetWithScale(WidgetUPtr widget, double scale)
{
Widget::addWidget(std::move(widget));
mScales.push_back(scale);

View file

@ -12,7 +12,7 @@ public:
void addWidget(WidgetUPtr widget) override;
void AddWidgetWithScale(WidgetUPtr widget, double scale);
void addWidgetWithScale(WidgetUPtr widget, double scale);
private:
void updateChildLocations() override;

View file

@ -1,6 +1,8 @@
list(APPEND visual_elements_LIB_INCLUDES
GeometryNode.cpp
RectangleNode.cpp
basic_shapes/RectangleNode.cpp
basic_shapes/CircleNode.cpp
basic_shapes/LineNode.cpp
MaterialNode.cpp
MeshNode.cpp
TextNode.cpp
@ -17,7 +19,8 @@ list(APPEND visual_elements_LIB_INCLUDES
add_library(visual_elements SHARED ${visual_elements_LIB_INCLUDES})
target_include_directories(visual_elements PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/basic_shapes
)
target_link_libraries(visual_elements PUBLIC core geometry fonts mesh image)

View file

@ -10,7 +10,9 @@ public:
Path,
Rectangle,
Circle,
Arc
Arc,
Line,
Polyline
};
public:
@ -29,4 +31,19 @@ protected:
bool mGeometryIsDirty{true};
};
class LineNode : public GeometryNode
{
public:
LineNode(const DiscretePoint& location)
: GeometryNode(location)
{
}
Type getType()
{
return Type::Line;
}
};
using GeometryNodePtr = std::unique_ptr<GeometryNode>;

View file

@ -0,0 +1,83 @@
#include "CircleNode.h"
#include "FontsManager.h"
#include "SceneModel.h"
#include "AbstractMesh.h"
#include "MeshPrimitives.h"
#include <iostream>
CircleNode::CircleNode(const DiscretePoint& location, unsigned radius)
: GeometryNode(location),
mRadius(radius)
{
}
CircleNode::Type CircleNode::getType()
{
return Type::Circle;
}
unsigned CircleNode::getRadius() const
{
return mRadius;
}
SceneItem* CircleNode::getSceneItem(std::size_t idx) const
{
if (idx == 0)
{
return mBackgroundItem.get();
}
else
{
return nullptr;
}
}
unsigned CircleNode::getNumSceneItems() const
{
return 1;
}
void CircleNode::setRadius(unsigned radius)
{
if (mRadius != radius)
{
mRadius = radius;
mTransformIsDirty = true;
}
}
void CircleNode::update(FontsManager* fontsManager)
{
if (!mBackgroundItem || mGeometryIsDirty)
{
auto mesh = MeshPrimitives::buildCircleAsTriMesh();
if (!mBackgroundItem)
{
mBackgroundItem = std::make_unique<SceneModel>(std::move(mesh));
mBackgroundItem->setName(mName + "_Model");
}
else
{
mBackgroundItem->updateMesh(std::move(mesh));
}
mGeometryIsDirty = false;
}
if (mTransformIsDirty)
{
mBackgroundItem->updateTransform({mLocation, static_cast<double>(2*mRadius), static_cast<double>(2*mRadius)});
mTransformIsDirty = false;
}
if (mMaterialIsDirty)
{
mBackgroundItem->updateUniformColor(mFillColor);
mMaterialIsDirty = false;
}
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "GeometryNode.h"
class CircleNode : public GeometryNode
{
public:
CircleNode(const DiscretePoint& location, unsigned radius);
Type getType();
unsigned getRadius() const;
SceneItem* getSceneItem(std::size_t idx) const override;
unsigned getNumSceneItems() const override;
void setRadius(unsigned radius);
void update(FontsManager* fontsManager) override;
private:
unsigned mRadius{1};
std::unique_ptr<SceneModel> mBackgroundItem;
std::unique_ptr<SceneModel> mOutlineItem;
};

View file

@ -1,9 +1,8 @@
#pragma once
#include <memory>
#include "GeometryNode.h"
#include "DiscretePoint.h"
#include <memory>
class RectangleNode : public GeometryNode
{