Squash commits for public release
This commit is contained in:
38
libs/libui/BUILD.gn
Normal file
38
libs/libui/BUILD.gn
Normal file
@@ -0,0 +1,38 @@
|
||||
import("//build/libs/TEMPLATE.gni")
|
||||
|
||||
xOS_static_library("libui") {
|
||||
sources = [
|
||||
"src/App.cpp",
|
||||
"src/Button.cpp",
|
||||
"src/ClientDecoder.cpp",
|
||||
"src/CollectionView.cpp",
|
||||
"src/Connection.cpp",
|
||||
"src/ContextManager.cpp",
|
||||
"src/Label.cpp",
|
||||
"src/MenuBar.cpp",
|
||||
"src/PopupMenu.cpp",
|
||||
"src/Responder.cpp",
|
||||
"src/ScrollView.cpp",
|
||||
"src/StackView.cpp",
|
||||
"src/TextField.cpp",
|
||||
"src/TextView.cpp",
|
||||
"src/View.cpp",
|
||||
"src/ViewController.cpp",
|
||||
"src/Window.cpp",
|
||||
"src/main.cpp",
|
||||
]
|
||||
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libipc",
|
||||
"libg",
|
||||
"libapi",
|
||||
"libfreetype",
|
||||
]
|
||||
configs = [ "//build/libs:libcxx_flags" ]
|
||||
|
||||
if (host == "llvm") {
|
||||
cflags = [ "-flto" ]
|
||||
}
|
||||
}
|
||||
54
libs/libui/include/libui/App.h
Normal file
54
libs/libui/include/libui/App.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libui/AppDelegate.h>
|
||||
#include <libui/Connection.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Window;
|
||||
|
||||
enum class AppState {
|
||||
Active,
|
||||
Background,
|
||||
};
|
||||
|
||||
class App : public LFoundation::EventReceiver {
|
||||
public:
|
||||
inline static App& the()
|
||||
{
|
||||
extern App* s_UI_App_the;
|
||||
return *s_UI_App_the;
|
||||
}
|
||||
|
||||
App();
|
||||
|
||||
inline int run() { return m_event_loop.run(); }
|
||||
inline LFoundation::EventLoop& event_loop() { return m_event_loop; }
|
||||
inline const LFoundation::EventLoop& event_loop() const { return m_event_loop; }
|
||||
void set_window(Window* window) { m_window = window; }
|
||||
inline Window& window() { return *m_window; }
|
||||
inline const Window& window() const { return *m_window; }
|
||||
|
||||
void set_delegate(AppDelegate* delegate) { m_delegate = delegate; }
|
||||
inline AppDelegate* delegate() { return m_delegate; }
|
||||
|
||||
inline Connection& connection() { return m_server_connection; }
|
||||
inline const Connection& connection() const { return m_server_connection; }
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
|
||||
void set_state(AppState state) { m_state = state; }
|
||||
AppState state() const { return m_state; }
|
||||
|
||||
private:
|
||||
LFoundation::EventLoop m_event_loop;
|
||||
Connection m_server_connection;
|
||||
AppDelegate* m_delegate { nullptr };
|
||||
Window* m_window { nullptr };
|
||||
AppState m_state;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
43
libs/libui/include/libui/AppDelegate.h
Normal file
43
libs/libui/include/libui/AppDelegate.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libg/Size.h>
|
||||
#include <libui/Connection.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define SET_APP_DELEGATE(name) \
|
||||
name* MainAppDelegatePtr; \
|
||||
extern "C" bool __init_app_delegate(UI::AppDelegate** res) \
|
||||
{ \
|
||||
MainAppDelegatePtr = new name(); \
|
||||
*res = MainAppDelegatePtr; \
|
||||
return MainAppDelegatePtr->application(); \
|
||||
}
|
||||
|
||||
namespace UI {
|
||||
|
||||
class AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size window_size() const
|
||||
{
|
||||
#ifdef TARGET_DESKTOP
|
||||
return preferred_desktop_window_size();
|
||||
#elif TARGET_MOBILE
|
||||
return LG::Size(320, 548);
|
||||
#endif
|
||||
}
|
||||
virtual LG::Size preferred_desktop_window_size() const { return LG::Size(400, 300); }
|
||||
virtual const char* icon_path() const { return "/res/icons/apps/missing.icon"; }
|
||||
virtual bool application() { return false; }
|
||||
virtual void application_will_terminate() { }
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
64
libs/libui/include/libui/Button.h
Normal file
64
libs/libui/include/libui/Button.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Constants/Text.h>
|
||||
#include <libui/Control.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <string>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Button : public Control {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
System,
|
||||
Custom,
|
||||
};
|
||||
|
||||
~Button() = default;
|
||||
|
||||
const std::string& title() const { return m_title; }
|
||||
void set_title(const std::string& title) { m_title = title, recalc_bounds(), set_needs_display(); }
|
||||
void set_title(std::string&& title) { m_title = std::move(title), recalc_bounds(), set_needs_display(); }
|
||||
|
||||
void set_title_color(const LG::Color& color) { m_title_color = color; }
|
||||
const LG::Color& title_color() const { return m_title_color; }
|
||||
|
||||
void set_content_edge_insets(const EdgeInsets& ei) { m_content_edge_insets = ei, recalc_bounds(); }
|
||||
const EdgeInsets& content_edge_insets() const { return m_content_edge_insets; }
|
||||
|
||||
void set_font(const LG::Font& font) { m_font = font, recalc_bounds(); }
|
||||
inline const LG::Font& font() const { return m_font; }
|
||||
|
||||
void set_alignment(Text::Alignment alignment) { m_alignment = alignment; }
|
||||
Text::Alignment alignment() const { return m_alignment; }
|
||||
|
||||
virtual void display(const LG::Rect& rect) override;
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override;
|
||||
virtual void mouse_exited() override;
|
||||
|
||||
virtual void mouse_down(const LG::Point<int>& location) override;
|
||||
virtual void mouse_up() override;
|
||||
|
||||
void set_type(Type type) { m_button_type = type; }
|
||||
|
||||
protected:
|
||||
Button(View* superview, const LG::Rect& frame);
|
||||
|
||||
private:
|
||||
void recalc_bounds();
|
||||
size_t text_height() const;
|
||||
size_t text_width();
|
||||
|
||||
Type m_button_type { Type::System };
|
||||
|
||||
std::string m_title {};
|
||||
LG::Color m_title_color { LG::Color::White };
|
||||
LG::Font m_font { LG::Font::system_font() };
|
||||
|
||||
Text::Alignment m_alignment { Text::Alignment::Left };
|
||||
EdgeInsets m_content_edge_insets { 12, 12, 12, 12 };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
36
libs/libui/include/libui/ClientDecoder.h
Normal file
36
libs/libui/include/libui/ClientDecoder.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <libapi/window_server/Connections/WSConnection.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class App;
|
||||
|
||||
class ClientDecoder : public BaseWindowClientDecoder {
|
||||
public:
|
||||
ClientDecoder();
|
||||
~ClientDecoder() = default;
|
||||
|
||||
using BaseWindowClientDecoder::handle;
|
||||
virtual std::unique_ptr<Message> handle(MouseMoveMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(MouseActionMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(MouseLeaveMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(MouseWheelMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(KeyboardMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(DisplayMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(WindowCloseRequestMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(ResizeMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(MenuBarActionMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(PopupActionMessage& msg) override;
|
||||
|
||||
// Notifiers
|
||||
virtual std::unique_ptr<Message> handle(NotifyWindowCreateMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(NotifyWindowStatusChangedMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(NotifyWindowIconChangedMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(NotifyWindowTitleChangedMessage& msg) override;
|
||||
|
||||
private:
|
||||
LFoundation::EventLoop& m_event_loop;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
74
libs/libui/include/libui/CollectionView.h
Normal file
74
libs/libui/include/libui/CollectionView.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <libg/Size.h>
|
||||
#include <libui/Constants/Layout.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/ScrollView.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
// The CollectionView in UIKit is devided into sections where each section contains a number
|
||||
// of elements. This behavior is not implemented, instead of CollectionViewDataSource protocol
|
||||
// just a callback CollectionViewRowStreamer is used. It returns the whole line to be rendered.
|
||||
typedef std::function<View*(int)> CollectionViewRowStreamer;
|
||||
|
||||
class CollectionView : public ScrollView {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
~CollectionView() = default;
|
||||
|
||||
template <class T>
|
||||
void set_data_source(T data_source) { m_data_source = data_source, prefetch_forward(); };
|
||||
|
||||
void reload_data() { prefetch_forward(); }
|
||||
void invalidate_row(int id);
|
||||
|
||||
virtual void display(const LG::Rect& rect) override;
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override;
|
||||
virtual void mouse_exited() override;
|
||||
virtual WheelEventResponse mouse_wheel_event(int wheel_data) override
|
||||
{
|
||||
ScrollView::mouse_wheel_event(wheel_data);
|
||||
if (wheel_data > 0) {
|
||||
after_scroll_forward();
|
||||
prefetch_forward();
|
||||
} else {
|
||||
after_scroll_backward();
|
||||
}
|
||||
|
||||
return WheelEventResponse::Handled;
|
||||
}
|
||||
|
||||
protected:
|
||||
CollectionView(View* superview, const LG::Rect&);
|
||||
CollectionView(View* superview, Window* window, const LG::Rect& frame);
|
||||
|
||||
private:
|
||||
enum PrefetchStatus {
|
||||
Success,
|
||||
EndOfStream,
|
||||
};
|
||||
PrefetchStatus prefetch_row_forward(int id);
|
||||
void prefetch_forward();
|
||||
void after_scroll_forward();
|
||||
void after_scroll_backward();
|
||||
|
||||
// Cache of views which precede the first on-screen view.
|
||||
std::list<View*> m_preceding_views {};
|
||||
|
||||
// Cache of views which follow the last on-screen view.
|
||||
std::list<View*> m_following_views {};
|
||||
std::list<View*> m_views_on_screen {};
|
||||
|
||||
size_t m_first_onscreen_row_index { 0 };
|
||||
size_t m_first_offscreen_row_index { 0 };
|
||||
LG::Point<int> m_next_frame_origin { 0, 16 };
|
||||
|
||||
CollectionViewRowStreamer m_data_source;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
75
libs/libui/include/libui/Common/MenuItem.h
Normal file
75
libs/libui/include/libui/Common/MenuItem.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <libipc/VectorEncoder.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Menu;
|
||||
class MenuBar;
|
||||
|
||||
class MenuItem {
|
||||
friend class Menu;
|
||||
friend class MenuBar;
|
||||
|
||||
public:
|
||||
MenuItem(const std::string& title, std::function<void()> action)
|
||||
: m_title(title)
|
||||
, m_action(action)
|
||||
{
|
||||
}
|
||||
|
||||
MenuItem(std::string&& title, std::function<void()> action)
|
||||
: m_title(std::move(title))
|
||||
, m_action(action)
|
||||
{
|
||||
}
|
||||
|
||||
inline const std::string& title() const { return m_title; }
|
||||
inline void invoke() { m_action(); }
|
||||
|
||||
private:
|
||||
inline void set_id(uint32_t id) { m_id = id; }
|
||||
|
||||
std::string m_title;
|
||||
std::function<void()> m_action;
|
||||
uint32_t m_id;
|
||||
};
|
||||
|
||||
class Menu {
|
||||
friend class MenuBar;
|
||||
|
||||
public:
|
||||
Menu() = default;
|
||||
Menu(const std::string& title)
|
||||
: m_title(title)
|
||||
{
|
||||
}
|
||||
|
||||
Menu(std::string&& title)
|
||||
: m_title(std::move(title))
|
||||
{
|
||||
}
|
||||
|
||||
inline void add_item(const MenuItem& item)
|
||||
{
|
||||
int id = m_menu_items.size();
|
||||
m_menu_items.push_back(item);
|
||||
m_menu_items.back().set_id(id);
|
||||
}
|
||||
|
||||
inline const std::string& title() const { return m_title; }
|
||||
inline uint32_t menu_id() const { return m_menu_id; }
|
||||
std::vector<MenuItem>& items() { return m_menu_items; }
|
||||
const std::vector<MenuItem>& items() const { return m_menu_items; }
|
||||
|
||||
private:
|
||||
inline void set_menu_id(uint32_t id) { m_menu_id = id; }
|
||||
|
||||
std::string m_title {};
|
||||
uint32_t m_menu_id;
|
||||
std::vector<MenuItem> m_menu_items;
|
||||
};
|
||||
|
||||
}
|
||||
37
libs/libui/include/libui/Connection.h
Normal file
37
libs/libui/include/libui/Connection.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <libipc/ClientConnection.h>
|
||||
#include <libui/ClientDecoder.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Window;
|
||||
|
||||
class Connection {
|
||||
public:
|
||||
static Connection& the();
|
||||
explicit Connection(const LIPC::DoubleSidedConnection&);
|
||||
|
||||
void greeting();
|
||||
int new_window(const Window& window);
|
||||
void set_buffer(const Window& window);
|
||||
|
||||
template <class T>
|
||||
inline std::unique_ptr<T> send_sync_message(const Message& msg) { return std::unique_ptr<T>(m_connection_with_server.send_sync(msg)); }
|
||||
inline bool send_async_message(const Message& msg) const { return m_connection_with_server.send_message(msg); }
|
||||
inline void listen() { m_connection_with_server.pump_messages(); }
|
||||
|
||||
// We use connection id as an unique key.
|
||||
inline int key() const { return m_connection_id; }
|
||||
|
||||
private:
|
||||
void setup_listners();
|
||||
|
||||
int m_connection_id;
|
||||
LIPC::DoubleSidedConnection m_connection;
|
||||
ClientConnection<BaseWindowServerDecoder, ClientDecoder> m_connection_with_server;
|
||||
BaseWindowServerDecoder m_server_decoder;
|
||||
ClientDecoder m_client_decoder;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
10
libs/libui/include/libui/Constants/Layout.h
Normal file
10
libs/libui/include/libui/Constants/Layout.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace UI::LayoutConstraints {
|
||||
|
||||
enum class Axis {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
|
||||
} // namespace UI::LayoutConstraints
|
||||
11
libs/libui/include/libui/Constants/Text.h
Normal file
11
libs/libui/include/libui/Constants/Text.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace UI::Text {
|
||||
|
||||
enum class Alignment {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
};
|
||||
|
||||
} // namespace UI::Text
|
||||
132
libs/libui/include/libui/Constraint.h
Normal file
132
libs/libui/include/libui/Constraint.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
namespace UI {
|
||||
class View;
|
||||
|
||||
class Constraint {
|
||||
public:
|
||||
enum class Attribute {
|
||||
NotAnAttr,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
CenterX,
|
||||
CenterY,
|
||||
Width,
|
||||
Height,
|
||||
};
|
||||
|
||||
enum class Relation {
|
||||
Equal,
|
||||
// LessThanOrEqual,
|
||||
// GreaterThanOrEqual,
|
||||
};
|
||||
|
||||
Constraint(View& item, Constraint::Attribute attr, Constraint::Relation related_by, View& rel_item, Constraint::Attribute to_attr, int multiplier, int constant)
|
||||
: m_item(&item)
|
||||
, m_attr(attr)
|
||||
, m_related_by(related_by)
|
||||
, m_rel_item(&rel_item)
|
||||
, m_to_attr(to_attr)
|
||||
, m_multiplier(multiplier)
|
||||
, m_constant(constant)
|
||||
{
|
||||
}
|
||||
|
||||
Constraint(View& item, Constraint::Attribute attr, Constraint::Relation related_by, int constant)
|
||||
: m_item(&item)
|
||||
, m_attr(attr)
|
||||
, m_related_by(related_by)
|
||||
, m_rel_item(nullptr)
|
||||
, m_to_attr(Constraint::Attribute::NotAnAttr)
|
||||
, m_multiplier(1)
|
||||
, m_constant(constant)
|
||||
{
|
||||
}
|
||||
|
||||
~Constraint() = default;
|
||||
|
||||
UI::View* item() const { return m_item; }
|
||||
Constraint::Attribute attribute() const { return m_attr; }
|
||||
Constraint::Relation relation() const { return m_related_by; }
|
||||
UI::View* rel_item() const { return m_rel_item; }
|
||||
Constraint::Attribute rel_attribute() const { return m_to_attr; }
|
||||
int multiplier() const { return m_multiplier; }
|
||||
int constant() const { return m_constant; }
|
||||
|
||||
template <typename T>
|
||||
static inline T get_attribute(const LG::Rect& rect, UI::Constraint::Attribute attr)
|
||||
{
|
||||
switch (attr) {
|
||||
|
||||
case UI::Constraint::Attribute::Top:
|
||||
return (T)rect.min_y();
|
||||
|
||||
case UI::Constraint::Attribute::Bottom:
|
||||
return (T)rect.max_y();
|
||||
|
||||
case UI::Constraint::Attribute::Left:
|
||||
return (T)rect.min_x();
|
||||
|
||||
case UI::Constraint::Attribute::Right:
|
||||
return (T)rect.max_x();
|
||||
|
||||
case UI::Constraint::Attribute::CenterX:
|
||||
return (T)rect.mid_x();
|
||||
|
||||
case UI::Constraint::Attribute::CenterY:
|
||||
return (T)rect.mid_y();
|
||||
|
||||
case UI::Constraint::Attribute::Width:
|
||||
return (T)rect.width();
|
||||
|
||||
case UI::Constraint::Attribute::Height:
|
||||
return (T)rect.height();
|
||||
|
||||
default:
|
||||
return (T)0;
|
||||
}
|
||||
}
|
||||
|
||||
template <UI::Constraint::Attribute attr, typename T>
|
||||
static constexpr inline void set_attribute(LG::Rect& rect, T m_value)
|
||||
{
|
||||
if constexpr (attr == UI::Constraint::Attribute::Top) {
|
||||
rect.set_y(m_value);
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::Bottom) {
|
||||
rect.set_y(m_value - rect.height());
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::Left) {
|
||||
rect.set_x(m_value);
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::Right) {
|
||||
rect.set_x(m_value - rect.width());
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::CenterX) {
|
||||
rect.set_x(m_value - (rect.width() / 2));
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::CenterY) {
|
||||
rect.set_y(m_value - (rect.height() / 2));
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::Width) {
|
||||
rect.set_width(m_value);
|
||||
return;
|
||||
} else if constexpr (attr == UI::Constraint::Attribute::Height) {
|
||||
rect.set_height(m_value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
UI::View* m_item;
|
||||
Constraint::Attribute m_attr;
|
||||
Constraint::Relation m_related_by;
|
||||
UI::View* m_rel_item;
|
||||
Constraint::Attribute m_to_attr;
|
||||
int m_multiplier { 0 };
|
||||
int m_constant { 0 };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
52
libs/libui/include/libui/Context.h
Normal file
52
libs/libui/include/libui/Context.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <libg/Context.h>
|
||||
#include <libui/ContextManager.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
namespace UI {
|
||||
class Context : public LG::Context {
|
||||
public:
|
||||
enum RelativeToCurrentContext {
|
||||
Yes
|
||||
};
|
||||
|
||||
explicit Context(LG::PixelBitmap& bitmap)
|
||||
: LG::Context(bitmap)
|
||||
{
|
||||
}
|
||||
|
||||
explicit Context(View& view)
|
||||
: Context(view.window()->bitmap())
|
||||
{
|
||||
auto frame = view.frame_in_window();
|
||||
add_clip(frame);
|
||||
set_draw_offset(frame.origin());
|
||||
}
|
||||
|
||||
Context(View& view, const LG::Rect& frame)
|
||||
: Context(view.window()->bitmap())
|
||||
{
|
||||
add_clip(frame);
|
||||
set_draw_offset(frame.origin());
|
||||
}
|
||||
|
||||
Context(View& view, RelativeToCurrentContext)
|
||||
: Context(view.window()->bitmap())
|
||||
{
|
||||
auto context_frame = view.frame();
|
||||
context_frame.offset_by(graphics_current_context().draw_offset());
|
||||
add_clip(context_frame);
|
||||
set_draw_offset(context_frame.origin());
|
||||
}
|
||||
|
||||
Context(View& view, const LG::Rect& frame, RelativeToCurrentContext)
|
||||
: Context(view.window()->bitmap())
|
||||
{
|
||||
auto context_frame = frame;
|
||||
context_frame.offset_by(graphics_current_context().draw_offset());
|
||||
add_clip(context_frame);
|
||||
set_draw_offset(context_frame.origin());
|
||||
}
|
||||
};
|
||||
} // namespace UI
|
||||
30
libs/libui/include/libui/ContextManager.h
Normal file
30
libs/libui/include/libui/ContextManager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <libg/Context.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
static inline void graphics_push_context(LG::Context&& context)
|
||||
{
|
||||
extern std::vector<LG::Context> s_ui_graphics_contexts;
|
||||
s_ui_graphics_contexts.push_back(std::move(context));
|
||||
}
|
||||
|
||||
static inline void graphics_push_context(const LG::Context& context)
|
||||
{
|
||||
extern std::vector<LG::Context> s_ui_graphics_contexts;
|
||||
s_ui_graphics_contexts.push_back(context);
|
||||
}
|
||||
|
||||
static inline void graphics_pop_context()
|
||||
{
|
||||
extern std::vector<LG::Context> s_ui_graphics_contexts;
|
||||
s_ui_graphics_contexts.pop_back();
|
||||
}
|
||||
|
||||
static inline LG::Context& graphics_current_context()
|
||||
{
|
||||
extern std::vector<LG::Context> s_ui_graphics_contexts;
|
||||
return s_ui_graphics_contexts.back();
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
98
libs/libui/include/libui/Control.h
Normal file
98
libs/libui/include/libui/Control.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libui/Event.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
namespace details {
|
||||
typedef std::function<void(UI::View*)> target_func_type;
|
||||
|
||||
class CallEvent final : public UI::Event {
|
||||
public:
|
||||
friend class Caller;
|
||||
|
||||
CallEvent(target_func_type callback, UI::View* view)
|
||||
: Event(Event::Type::UIHandlerInvoke)
|
||||
, m_callback(callback)
|
||||
, m_view(view)
|
||||
{
|
||||
}
|
||||
~CallEvent() = default;
|
||||
|
||||
UI::View* view() const { return m_view; }
|
||||
|
||||
private:
|
||||
UI::View* m_view;
|
||||
target_func_type m_callback;
|
||||
};
|
||||
|
||||
class Caller : public LFoundation::EventReceiver {
|
||||
public:
|
||||
friend class EventLoop;
|
||||
|
||||
Caller()
|
||||
: EventReceiver()
|
||||
{
|
||||
}
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override
|
||||
{
|
||||
switch (event->type()) {
|
||||
case UI::Event::Type::UIHandlerInvoke: {
|
||||
CallEvent& own_event = *(CallEvent*)event.get();
|
||||
own_event.m_callback(own_event.view());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace details
|
||||
|
||||
namespace UI {
|
||||
class Control : public View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
struct Target {
|
||||
details::target_func_type target_func;
|
||||
UI::Event::Type for_event;
|
||||
};
|
||||
|
||||
~Control() = default;
|
||||
|
||||
template <class ActionType>
|
||||
void add_target(ActionType target_func, UI::Event::Type for_event)
|
||||
{
|
||||
m_targets.push_back(Target { target_func, for_event });
|
||||
}
|
||||
|
||||
void send_actions(UI::Event::Type for_event)
|
||||
{
|
||||
auto& event_loop = LFoundation::EventLoop::the();
|
||||
for (auto& target : m_targets) {
|
||||
if (target.for_event == for_event) {
|
||||
event_loop.add(m_caller, new details::CallEvent(target.target_func, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Target>& targets() { return m_targets; }
|
||||
const std::vector<Target>& targets() const { return m_targets; }
|
||||
|
||||
protected:
|
||||
Control(View* superview, const LG::Rect& r)
|
||||
: View(superview, r)
|
||||
{
|
||||
}
|
||||
Control(View* superview, Window* window, const LG::Rect& r) = delete;
|
||||
|
||||
private:
|
||||
std::vector<Target> m_targets {};
|
||||
bool is_selected { false };
|
||||
bool is_enabled { false };
|
||||
details::Caller m_caller {};
|
||||
};
|
||||
} // namespace UI
|
||||
28
libs/libui/include/libui/EdgeInsets.h
Normal file
28
libs/libui/include/libui/EdgeInsets.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace UI {
|
||||
|
||||
class EdgeInsets {
|
||||
public:
|
||||
EdgeInsets() = default;
|
||||
EdgeInsets(int top, int left, int bottom, int right)
|
||||
: m_top(top)
|
||||
, m_left(left)
|
||||
, m_bottom(bottom)
|
||||
, m_right(right)
|
||||
{
|
||||
}
|
||||
|
||||
inline int top() const { return m_top; }
|
||||
inline int left() const { return m_left; }
|
||||
inline int bottom() const { return m_bottom; }
|
||||
inline int right() const { return m_right; }
|
||||
|
||||
private:
|
||||
int m_top { 0 };
|
||||
int m_bottom { 0 };
|
||||
int m_left { 0 };
|
||||
int m_right { 0 };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
383
libs/libui/include/libui/Event.h
Normal file
383
libs/libui/include/libui/Event.h
Normal file
@@ -0,0 +1,383 @@
|
||||
#pragma once
|
||||
|
||||
#include <libapi/window_server/MessageContent/MouseAction.h>
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Event : public LFoundation::Event {
|
||||
public:
|
||||
// Some of events can be sent and are used in processing of data,
|
||||
// while some part of events are used only for describing actions.
|
||||
enum Type {
|
||||
Invalid = 0x2000,
|
||||
MouseEvent,
|
||||
MouseUpEvent,
|
||||
MouseDownEvent,
|
||||
MouseActionEvent,
|
||||
MouseEnterEvent,
|
||||
MouseLeaveEvent,
|
||||
MouseWheelEvent,
|
||||
KeyUpEvent,
|
||||
KeyDownEvent,
|
||||
DisplayEvent,
|
||||
LayoutEvent,
|
||||
WindowCloseRequestEvent,
|
||||
ResizeEvent,
|
||||
MenuBarActionEvent,
|
||||
PopupActionEvent,
|
||||
|
||||
UIHandlerInvoke,
|
||||
|
||||
NotifyWindowCreateEvent,
|
||||
NotifyWindowStatusChangedEvent,
|
||||
NotifyWindowIconChangedEvent,
|
||||
NotifyWindowTitleChangedEvent,
|
||||
|
||||
ViewDidLoad,
|
||||
|
||||
Other,
|
||||
};
|
||||
|
||||
explicit Event(int type)
|
||||
: LFoundation::Event(type)
|
||||
{
|
||||
}
|
||||
|
||||
~Event() = default;
|
||||
};
|
||||
|
||||
class MouseEvent : public Event {
|
||||
public:
|
||||
MouseEvent(uint32_t x, uint32_t y)
|
||||
: Event(Event::Type::MouseEvent)
|
||||
, m_x(x)
|
||||
, m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
~MouseEvent() = default;
|
||||
|
||||
uint32_t x() const { return m_x; }
|
||||
uint32_t y() const { return m_y; }
|
||||
|
||||
private:
|
||||
uint32_t m_x;
|
||||
uint32_t m_y;
|
||||
};
|
||||
|
||||
class MouseActionEvent : public Event {
|
||||
public:
|
||||
MouseActionEvent(MouseActionType type, uint32_t x, uint32_t y)
|
||||
: Event(Event::Type::MouseActionEvent)
|
||||
, m_type(type)
|
||||
, m_x(x)
|
||||
, m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
~MouseActionEvent() = default;
|
||||
|
||||
MouseActionType type() const { return m_type; }
|
||||
uint32_t x() const { return m_x; }
|
||||
uint32_t y() const { return m_y; }
|
||||
|
||||
private:
|
||||
MouseActionType m_type;
|
||||
uint32_t m_x;
|
||||
uint32_t m_y;
|
||||
};
|
||||
|
||||
class MouseLeaveEvent : public Event {
|
||||
public:
|
||||
MouseLeaveEvent(uint32_t x, uint32_t y)
|
||||
: Event(Event::Type::MouseLeaveEvent)
|
||||
, m_x(x)
|
||||
, m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
~MouseLeaveEvent() = default;
|
||||
|
||||
uint32_t x() const { return m_x; }
|
||||
uint32_t y() const { return m_y; }
|
||||
|
||||
private:
|
||||
uint32_t m_x;
|
||||
uint32_t m_y;
|
||||
};
|
||||
|
||||
class MouseWheelEvent : public Event {
|
||||
public:
|
||||
MouseWheelEvent(uint32_t x, uint32_t y, int data)
|
||||
: Event(Event::Type::MouseWheelEvent)
|
||||
, m_x(x)
|
||||
, m_y(y)
|
||||
, m_wheel_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
~MouseWheelEvent() = default;
|
||||
|
||||
uint32_t x() const { return m_x; }
|
||||
uint32_t y() const { return m_y; }
|
||||
int wheel_data() const { return m_wheel_data; }
|
||||
|
||||
private:
|
||||
uint32_t m_x;
|
||||
uint32_t m_y;
|
||||
int m_wheel_data;
|
||||
};
|
||||
|
||||
typedef uint32_t key_t;
|
||||
class KeyUpEvent : public Event {
|
||||
public:
|
||||
KeyUpEvent(key_t key)
|
||||
: Event(Event::Type::KeyUpEvent)
|
||||
, m_key(key)
|
||||
{
|
||||
}
|
||||
|
||||
~KeyUpEvent() = default;
|
||||
|
||||
key_t key() const { return m_key; }
|
||||
|
||||
private:
|
||||
key_t m_key;
|
||||
};
|
||||
|
||||
class KeyDownEvent : public Event {
|
||||
public:
|
||||
KeyDownEvent(key_t key)
|
||||
: Event(Event::Type::KeyDownEvent)
|
||||
, m_key(key)
|
||||
{
|
||||
}
|
||||
|
||||
~KeyDownEvent() = default;
|
||||
|
||||
key_t key() const { return m_key; }
|
||||
|
||||
private:
|
||||
key_t m_key;
|
||||
};
|
||||
|
||||
class DisplayEvent : public Event {
|
||||
public:
|
||||
DisplayEvent(const LG::Rect& rect)
|
||||
: Event(Event::Type::DisplayEvent)
|
||||
, m_display_bounds(rect)
|
||||
{
|
||||
}
|
||||
|
||||
~DisplayEvent() = default;
|
||||
|
||||
LG::Rect& bounds() { return m_display_bounds; }
|
||||
const LG::Rect& bounds() const { return m_display_bounds; }
|
||||
|
||||
private:
|
||||
LG::Rect m_display_bounds;
|
||||
};
|
||||
|
||||
class View;
|
||||
class LayoutEvent : public Event {
|
||||
public:
|
||||
LayoutEvent(View* rect)
|
||||
: Event(Event::Type::LayoutEvent)
|
||||
, m_target(rect)
|
||||
{
|
||||
}
|
||||
|
||||
~LayoutEvent() = default;
|
||||
|
||||
View* target() const { return m_target; }
|
||||
|
||||
private:
|
||||
View* m_target;
|
||||
};
|
||||
|
||||
class WindowCloseRequestEvent : public Event {
|
||||
public:
|
||||
WindowCloseRequestEvent(uint32_t window_id)
|
||||
: Event(Event::Type::WindowCloseRequestEvent)
|
||||
, m_window_id(window_id)
|
||||
{
|
||||
}
|
||||
|
||||
~WindowCloseRequestEvent() = default;
|
||||
uint32_t window_id() const { return m_window_id; }
|
||||
|
||||
private:
|
||||
uint32_t m_window_id;
|
||||
};
|
||||
|
||||
class ResizeEvent : public Event {
|
||||
public:
|
||||
ResizeEvent(uint32_t window_id, const LG::Rect& bounds)
|
||||
: Event(Event::Type::ResizeEvent)
|
||||
, m_window_id(window_id)
|
||||
, m_bounds(bounds)
|
||||
{
|
||||
}
|
||||
|
||||
~ResizeEvent() = default;
|
||||
uint32_t window_id() const { return m_window_id; }
|
||||
const LG::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
private:
|
||||
uint32_t m_window_id;
|
||||
LG::Rect m_bounds;
|
||||
};
|
||||
|
||||
class MenuBarActionEvent : public Event {
|
||||
public:
|
||||
MenuBarActionEvent(uint32_t window_id, int menu_id, int item_id)
|
||||
: Event(Event::Type::MenuBarActionEvent)
|
||||
, m_window_id(window_id)
|
||||
, m_menu_id(menu_id)
|
||||
, m_item_id(item_id)
|
||||
{
|
||||
}
|
||||
|
||||
~MenuBarActionEvent() = default;
|
||||
uint32_t window_id() const { return m_window_id; }
|
||||
int menu_id() const { return m_menu_id; }
|
||||
int item_id() const { return m_item_id; }
|
||||
|
||||
private:
|
||||
uint32_t m_window_id;
|
||||
int m_menu_id;
|
||||
int m_item_id;
|
||||
};
|
||||
|
||||
class PopupActionEvent : public Event {
|
||||
public:
|
||||
PopupActionEvent(uint32_t window_id, int menu_id, int item_id)
|
||||
: Event(Event::Type::PopupActionEvent)
|
||||
, m_window_id(window_id)
|
||||
, m_menu_id(menu_id)
|
||||
, m_item_id(item_id)
|
||||
{
|
||||
}
|
||||
|
||||
~PopupActionEvent() = default;
|
||||
uint32_t window_id() const { return m_window_id; }
|
||||
int menu_id() const { return m_menu_id; }
|
||||
int item_id() const { return m_item_id; }
|
||||
|
||||
private:
|
||||
uint32_t m_window_id;
|
||||
int m_menu_id;
|
||||
int m_item_id;
|
||||
};
|
||||
|
||||
// Notifiers
|
||||
class NotifyWindowCreateEvent : public Event {
|
||||
public:
|
||||
NotifyWindowCreateEvent(std::string&& bundle_id, std::string&& icon_path, uint32_t changed_window_id, int changed_window_type)
|
||||
: Event(Event::Type::NotifyWindowCreateEvent)
|
||||
, m_window_id(changed_window_id)
|
||||
, m_icon_path(icon_path)
|
||||
, m_bundle_id(bundle_id)
|
||||
, m_window_type(changed_window_type)
|
||||
{
|
||||
}
|
||||
|
||||
~NotifyWindowCreateEvent() = default;
|
||||
uint32_t window_id() const { return m_window_id; }
|
||||
int window_type() const { return m_window_type; }
|
||||
const std::string& icon_path() const { return m_icon_path; }
|
||||
const std::string& bundle_id() const { return m_bundle_id; }
|
||||
|
||||
private:
|
||||
int m_window_id;
|
||||
int m_window_type;
|
||||
std::string m_bundle_id;
|
||||
std::string m_icon_path;
|
||||
};
|
||||
|
||||
class NotifyWindowStatusChangedEvent : public Event {
|
||||
public:
|
||||
NotifyWindowStatusChangedEvent(uint32_t changed_window_id, int type)
|
||||
: Event(Event::Type::NotifyWindowStatusChangedEvent)
|
||||
, m_changed_window_id(changed_window_id)
|
||||
, m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
~NotifyWindowStatusChangedEvent() = default;
|
||||
uint32_t changed_window_id() const { return m_changed_window_id; }
|
||||
int type() const { return m_type; }
|
||||
|
||||
private:
|
||||
uint32_t m_changed_window_id;
|
||||
int m_type;
|
||||
};
|
||||
|
||||
class NotifyWindowIconChangedEvent : public Event {
|
||||
public:
|
||||
NotifyWindowIconChangedEvent(uint32_t changed_window_id, const std::string& path)
|
||||
: Event(Event::Type::NotifyWindowIconChangedEvent)
|
||||
, m_changed_window_id(changed_window_id)
|
||||
, m_icon_path(path)
|
||||
{
|
||||
}
|
||||
|
||||
NotifyWindowIconChangedEvent(uint32_t changed_window_id, std::string&& path)
|
||||
: Event(Event::Type::NotifyWindowIconChangedEvent)
|
||||
, m_changed_window_id(changed_window_id)
|
||||
, m_icon_path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
~NotifyWindowIconChangedEvent() = default;
|
||||
uint32_t changed_window_id() const { return m_changed_window_id; }
|
||||
const std::string& icon_path() const { return m_icon_path; }
|
||||
|
||||
private:
|
||||
uint32_t m_changed_window_id;
|
||||
std::string m_icon_path;
|
||||
};
|
||||
|
||||
class NotifyWindowTitleChangedEvent : public Event {
|
||||
public:
|
||||
NotifyWindowTitleChangedEvent(uint32_t changed_window_id, const std::string& title)
|
||||
: Event(Event::Type::NotifyWindowTitleChangedEvent)
|
||||
, m_changed_window_id(changed_window_id)
|
||||
, m_title(title)
|
||||
{
|
||||
}
|
||||
|
||||
NotifyWindowTitleChangedEvent(uint32_t changed_window_id, std::string&& title)
|
||||
: Event(Event::Type::NotifyWindowTitleChangedEvent)
|
||||
, m_changed_window_id(changed_window_id)
|
||||
, m_title(std::move(title))
|
||||
{
|
||||
}
|
||||
|
||||
~NotifyWindowTitleChangedEvent() = default;
|
||||
uint32_t changed_window_id() const { return m_changed_window_id; }
|
||||
const std::string& title() const { return m_title; }
|
||||
|
||||
private:
|
||||
uint32_t m_changed_window_id;
|
||||
std::string m_title;
|
||||
};
|
||||
|
||||
// View Life Cycle Events
|
||||
class ViewDidLoadEvent : public Event {
|
||||
public:
|
||||
ViewDidLoadEvent()
|
||||
: Event(Event::Type::ViewDidLoad)
|
||||
{
|
||||
}
|
||||
|
||||
~ViewDidLoadEvent() = default;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
42
libs/libui/include/libui/GestureManager.h
Normal file
42
libs/libui/include/libui/GestureManager.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include <libui/GestureRecognizer.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class GestureManager final {
|
||||
public:
|
||||
GestureManager() = default;
|
||||
~GestureManager()
|
||||
{
|
||||
for (auto* gst : m_handlers) {
|
||||
delete gst;
|
||||
}
|
||||
}
|
||||
|
||||
inline void add(GestureRecognizer* recon) { m_handlers.push_back(recon); }
|
||||
void mouse_up()
|
||||
{
|
||||
for (auto* handler : m_handlers) {
|
||||
handler->mouse_up();
|
||||
}
|
||||
}
|
||||
|
||||
void mouse_down(const LG::Point<int>& location)
|
||||
{
|
||||
for (auto* handler : m_handlers) {
|
||||
handler->mouse_down(location);
|
||||
}
|
||||
}
|
||||
|
||||
void mouse_moved(const LG::Point<int>& new_location)
|
||||
{
|
||||
for (auto* handler : m_handlers) {
|
||||
handler->mouse_moved(new_location);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<GestureRecognizer*> m_handlers;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
138
libs/libui/include/libui/GestureRecognizer.h
Normal file
138
libs/libui/include/libui/GestureRecognizer.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libui/Event.h>
|
||||
#include <vector>
|
||||
|
||||
namespace UI {
|
||||
class GestureRecognizer;
|
||||
}
|
||||
|
||||
// TODO: This invoker is really similar to what we can see in Control.h,
|
||||
// maybe move out them to Inovokers.h or smth like that?
|
||||
|
||||
namespace GestureDetails {
|
||||
typedef std::function<void(const UI::GestureRecognizer*)> target_func_type;
|
||||
|
||||
class CallEvent final : public UI::Event {
|
||||
public:
|
||||
friend class Caller;
|
||||
|
||||
CallEvent(target_func_type callback, const UI::GestureRecognizer* recon)
|
||||
: Event(Event::Type::UIHandlerInvoke)
|
||||
, m_callback(callback)
|
||||
, m_recognizer(recon)
|
||||
{
|
||||
}
|
||||
~CallEvent() = default;
|
||||
|
||||
const UI::GestureRecognizer* recognizer() const { return m_recognizer; }
|
||||
|
||||
private:
|
||||
const UI::GestureRecognizer* m_recognizer;
|
||||
target_func_type m_callback;
|
||||
};
|
||||
|
||||
class Caller : public LFoundation::EventReceiver {
|
||||
public:
|
||||
friend class EventLoop;
|
||||
|
||||
Caller()
|
||||
: EventReceiver()
|
||||
{
|
||||
}
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override
|
||||
{
|
||||
switch (event->type()) {
|
||||
case UI::Event::Type::UIHandlerInvoke: {
|
||||
CallEvent& own_event = *(CallEvent*)event.get();
|
||||
own_event.m_callback(own_event.recognizer());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace details
|
||||
|
||||
namespace UI {
|
||||
|
||||
class GestureRecognizer {
|
||||
public:
|
||||
enum State {
|
||||
Possible,
|
||||
Began,
|
||||
Cancelled,
|
||||
Ended,
|
||||
};
|
||||
|
||||
GestureRecognizer(GestureDetails::target_func_type target)
|
||||
: m_target(target)
|
||||
{
|
||||
}
|
||||
virtual ~GestureRecognizer() = default;
|
||||
|
||||
State state() const { return m_state; }
|
||||
virtual void mouse_up() { }
|
||||
virtual void mouse_down(const LG::Point<int>&) { }
|
||||
virtual void mouse_moved(const LG::Point<int>&) { }
|
||||
|
||||
protected:
|
||||
void update_state(State newstate)
|
||||
{
|
||||
m_state = newstate;
|
||||
did_update_state();
|
||||
}
|
||||
|
||||
void did_update_state()
|
||||
{
|
||||
LFoundation::EventLoop::the().add(m_caller, new GestureDetails::CallEvent(m_target, this));
|
||||
}
|
||||
|
||||
private:
|
||||
State m_state { State::Ended };
|
||||
GestureDetails::target_func_type m_target;
|
||||
GestureDetails::Caller m_caller {};
|
||||
};
|
||||
|
||||
class SwipeGestureRecognizer final : public GestureRecognizer {
|
||||
public:
|
||||
SwipeGestureRecognizer(GestureDetails::target_func_type target)
|
||||
: GestureRecognizer(target)
|
||||
{
|
||||
}
|
||||
~SwipeGestureRecognizer() = default;
|
||||
|
||||
virtual void mouse_up() override
|
||||
{
|
||||
if (m_was_moved && m_was_down) {
|
||||
update_state(State::Ended);
|
||||
} else {
|
||||
update_state(State::Cancelled);
|
||||
}
|
||||
m_was_down = false;
|
||||
m_was_moved = false;
|
||||
}
|
||||
|
||||
virtual void mouse_down(const LG::Point<int>&) override
|
||||
{
|
||||
m_was_down = true;
|
||||
update_state(State::Possible);
|
||||
}
|
||||
|
||||
virtual void mouse_moved(const LG::Point<int>&) override
|
||||
{
|
||||
if (state() == State::Possible && m_was_down) {
|
||||
update_state(State::Began);
|
||||
m_was_moved = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_was_moved { false };
|
||||
bool m_was_down { false };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
53
libs/libui/include/libui/Label.h
Normal file
53
libs/libui/include/libui/Label.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Constants/Text.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Label : public View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
~Label() = default;
|
||||
|
||||
void set_text(const std::string& text) { m_text = text, set_needs_display(); }
|
||||
void set_text(std::string&& text) { m_text = std::move(text), set_needs_display(); }
|
||||
const std::string& text() const { return m_text; }
|
||||
|
||||
void set_text_color(const LG::Color& color) { m_text_color = color; }
|
||||
const LG::Color& text_color() const { return m_text_color; }
|
||||
|
||||
void set_content_edge_insets(const EdgeInsets& ei) { m_content_edge_insets = ei; }
|
||||
const EdgeInsets& content_edge_insets() const { return m_content_edge_insets; }
|
||||
|
||||
void set_alignment(Text::Alignment alignment) { m_alignment = alignment; }
|
||||
Text::Alignment alignment() const { return m_alignment; }
|
||||
|
||||
void set_font(const LG::Font& font) { m_font = font, set_needs_display(); }
|
||||
inline const LG::Font& font() const { return m_font; }
|
||||
|
||||
inline size_t preferred_width() const { return text_width() + m_content_edge_insets.left() + m_content_edge_insets.right(); }
|
||||
|
||||
virtual void display(const LG::Rect& rect) override;
|
||||
|
||||
protected:
|
||||
Label(View* superview, const LG::Rect&);
|
||||
Label(View* superview, Window* window, const LG::Rect&);
|
||||
|
||||
private:
|
||||
void recalc_bounds();
|
||||
size_t text_height() const;
|
||||
size_t text_width() const;
|
||||
|
||||
std::string m_text {};
|
||||
LG::Color m_text_color { LG::Color::Black };
|
||||
LG::Font m_font { LG::Font::system_font() };
|
||||
|
||||
Text::Alignment m_alignment { Text::Alignment::Left };
|
||||
EdgeInsets m_content_edge_insets {};
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
34
libs/libui/include/libui/Layer.h
Normal file
34
libs/libui/include/libui/Layer.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Constants/Text.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Layer {
|
||||
public:
|
||||
Layer() = default;
|
||||
~Layer() = default;
|
||||
|
||||
void set_corner_mask(const LG::CornerMask& cm) { m_corner_mask = cm; }
|
||||
const LG::CornerMask& corner_mask() const { return m_corner_mask; }
|
||||
|
||||
void set_shading(const LG::Shading& sh) { m_shading = sh; }
|
||||
const LG::Shading& shading() const { return m_shading; }
|
||||
|
||||
void display(const LG::Rect& rect, const LG::Rect& frame) const
|
||||
{
|
||||
LG::Context ctx = graphics_current_context();
|
||||
ctx.set_fill_color(LG::Color(100, 100, 100, 40));
|
||||
ctx.add_clip(rect);
|
||||
ctx.draw_box_shading(frame, m_shading, m_corner_mask);
|
||||
}
|
||||
|
||||
private:
|
||||
LG::CornerMask m_corner_mask {};
|
||||
LG::Shading m_shading {};
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
31
libs/libui/include/libui/MenuBar.h
Normal file
31
libs/libui/include/libui/MenuBar.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <libapi/window_server/MessageContent/MouseAction.h>
|
||||
#include <libui/Common/MenuItem.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Window;
|
||||
class MenuBar {
|
||||
friend class Window;
|
||||
|
||||
public:
|
||||
MenuBar() = default;
|
||||
~MenuBar() = default;
|
||||
|
||||
void add_menu(const Menu&);
|
||||
void add_menu(Menu&&);
|
||||
|
||||
std::vector<Menu>& menus() { return m_menus; }
|
||||
const std::vector<Menu>& menus() const { return m_menus; }
|
||||
|
||||
private:
|
||||
void set_host_window_id(int win_id) { m_host_window_id = win_id; }
|
||||
int host_window_id() const { return m_host_window_id; }
|
||||
|
||||
int m_host_window_id { -1 };
|
||||
std::vector<Menu> m_menus;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
29
libs/libui/include/libui/PopupMenu.h
Normal file
29
libs/libui/include/libui/PopupMenu.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <libui/Common/MenuItem.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Window;
|
||||
class PopupMenu {
|
||||
friend class Window;
|
||||
|
||||
public:
|
||||
PopupMenu() = default;
|
||||
~PopupMenu() = default;
|
||||
|
||||
void show(LG::Point<int>, const Menu& menu);
|
||||
void show(LG::Point<int>, Menu&& menu);
|
||||
|
||||
Menu& menu() { return m_menu; }
|
||||
const Menu& menu() const { return m_menu; }
|
||||
|
||||
private:
|
||||
void set_host_window_id(int win_id) { m_host_window_id = win_id; }
|
||||
|
||||
Menu m_menu;
|
||||
uint32_t m_host_window_id;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
32
libs/libui/include/libui/Responder.h
Normal file
32
libs/libui/include/libui/Responder.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <libfoundation/Object.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <libui/Event.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Window;
|
||||
|
||||
class Responder : public LFoundation::Object {
|
||||
public:
|
||||
bool send_invalidate_message_to_server(const LG::Rect& rect) const;
|
||||
void send_display_message_to_self(Window& win, const LG::Rect& display_rect);
|
||||
void send_layout_message(Window& win, UI::View* for_view);
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
virtual void receive_mouse_move_event(MouseEvent&) { }
|
||||
virtual void receive_mouse_action_event(MouseActionEvent&) { }
|
||||
virtual void receive_mouse_leave_event(MouseLeaveEvent&) { }
|
||||
virtual bool receive_mouse_wheel_event(MouseWheelEvent&) { return false; }
|
||||
virtual void receive_keyup_event(KeyUpEvent&) { }
|
||||
virtual void receive_keydown_event(KeyDownEvent&) { }
|
||||
virtual void receive_display_event(DisplayEvent&) { m_display_message_sent = false; }
|
||||
virtual bool receive_layout_event(const LayoutEvent&, bool force_layout_if_not_target = false) { return false; }
|
||||
|
||||
protected:
|
||||
bool m_display_message_sent { false };
|
||||
LG::Rect m_prev_display_message {};
|
||||
Responder() = default;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
50
libs/libui/include/libui/Screen.h
Normal file
50
libs/libui/include/libui/Screen.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <string>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Screen;
|
||||
static Screen* __main_screen;
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
Screen()
|
||||
: m_screen_fd(open("/dev/bga", O_RDONLY))
|
||||
, m_bounds(0, 0, ioctl(m_screen_fd, BGA_GET_WIDTH, 0), ioctl(m_screen_fd, BGA_GET_HEIGHT, 0))
|
||||
, m_scale(ioctl(m_screen_fd, BGA_GET_SCALE, 0))
|
||||
{
|
||||
}
|
||||
|
||||
explicit Screen(const std::string& path)
|
||||
: m_screen_fd(open(path.c_str(), O_RDONLY))
|
||||
, m_bounds(0, 0, ioctl(m_screen_fd, BGA_GET_WIDTH, 0), ioctl(m_screen_fd, BGA_GET_HEIGHT, 0))
|
||||
, m_scale(ioctl(m_screen_fd, BGA_GET_SCALE, 0))
|
||||
{
|
||||
}
|
||||
|
||||
~Screen() = default;
|
||||
|
||||
int scale() const { return m_scale; }
|
||||
const LG::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
static Screen& main()
|
||||
{
|
||||
if (!__main_screen) {
|
||||
__main_screen = new UI::Screen();
|
||||
}
|
||||
return *__main_screen;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_screen_fd;
|
||||
int m_scale { 1 };
|
||||
LG::Rect m_bounds;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
57
libs/libui/include/libui/ScrollView.h
Normal file
57
libs/libui/include/libui/ScrollView.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include <libg/Size.h>
|
||||
#include <libui/Constants/Layout.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class ScrollView : public View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
~ScrollView() = default;
|
||||
|
||||
inline const LG::Size& content_size() const { return m_content_size; }
|
||||
inline LG::Size& content_size() { return m_content_size; }
|
||||
inline const LG::Point<int>& content_offset() const { return m_content_offset; }
|
||||
inline LG::Point<int>& content_offset() { return m_content_offset; }
|
||||
|
||||
virtual std::optional<View*> subview_at(const LG::Point<int>& point) const override;
|
||||
|
||||
virtual void display(const LG::Rect& rect) override;
|
||||
virtual WheelEventResponse mouse_wheel_event(int wheel_data) override;
|
||||
virtual void receive_mouse_move_event(MouseEvent&) override;
|
||||
virtual void receive_display_event(DisplayEvent&) override;
|
||||
|
||||
protected:
|
||||
ScrollView(View* superview, const LG::Rect&);
|
||||
ScrollView(View* superview, Window* window, const LG::Rect& frame);
|
||||
|
||||
void display_scroll_indicators(LG::Context&);
|
||||
|
||||
// The location of a subview relativly to its superview could
|
||||
// differ from it's frame() (e.g when scrolling), to determine
|
||||
// the right location we ask the superview to return it.
|
||||
virtual LG::Point<int> subview_location(const View& subview) const override;
|
||||
|
||||
private:
|
||||
void setup_scroll_animation(int wheel_data);
|
||||
void do_scroll_animation_step();
|
||||
void rearm_scroll_animation();
|
||||
void do_scroll(int n_x, int n_y);
|
||||
void recalc_content_props();
|
||||
|
||||
LG::Size m_content_size {};
|
||||
LG::Point<int> m_content_offset {};
|
||||
LG::Point<int> m_mouse_location {};
|
||||
|
||||
bool m_has_timer { false };
|
||||
size_t m_scroll_velocity { 0 };
|
||||
size_t m_last_scroll_multiplier { 0 };
|
||||
LG::Point<int> m_animation_target { 0, 0 };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
78
libs/libui/include/libui/StackView.h
Normal file
78
libs/libui/include/libui/StackView.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Constants/Layout.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class StackView : public View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
enum class Distribution {
|
||||
Standard,
|
||||
FillEqually,
|
||||
EqualSpacing,
|
||||
EqualCentering,
|
||||
};
|
||||
|
||||
enum class Alignment {
|
||||
Leading,
|
||||
Center,
|
||||
Trailing,
|
||||
};
|
||||
|
||||
~StackView() = default;
|
||||
|
||||
template <class T, class... Args>
|
||||
T& add_arranged_subview(Args&&... args)
|
||||
{
|
||||
T& view = add_subview<T>(LG::Rect(0, 0, 0, 0), std::forward<Args>(args)...);
|
||||
m_views.push_back(&view);
|
||||
return view;
|
||||
}
|
||||
|
||||
const std::vector<View*>& arranged_subviews() const { return m_views; }
|
||||
std::vector<View*>& arranged_subviews() { return m_views; }
|
||||
|
||||
void set_axis(LayoutConstraints::Axis axis) { m_axis = axis; }
|
||||
LayoutConstraints::Axis axis() const { return m_axis; }
|
||||
|
||||
void set_distribution(Distribution dist) { m_distribution = dist; }
|
||||
Distribution distribution() const { return m_distribution; }
|
||||
|
||||
void set_alignment(Alignment alignment) { m_alignment = alignment; }
|
||||
Alignment alignment() const { return m_alignment; }
|
||||
|
||||
void set_spacing(size_t spacing) { m_spacing = spacing; }
|
||||
size_t spacing() const { return m_spacing; }
|
||||
|
||||
virtual bool receive_layout_event(const LayoutEvent&, bool force_layout_if_not_target = false) override;
|
||||
|
||||
protected:
|
||||
StackView(View* superview, const LG::Rect&);
|
||||
StackView(View* superview, Window* window, const LG::Rect& frame);
|
||||
|
||||
private:
|
||||
void recalc_subviews_positions();
|
||||
void recalc_fill_equally();
|
||||
size_t recalc_total_content_width();
|
||||
size_t recalc_total_content_height();
|
||||
size_t recalc_subview_min_x(View*);
|
||||
size_t recalc_subview_min_y(View*);
|
||||
size_t recalc_spacing();
|
||||
size_t recalc_initial_spacing(size_t element_spacing);
|
||||
size_t recalc_equal_spacing_horizontal() { return m_views.size() > 2 ? (bounds().width() - recalc_total_content_width()) / (m_views.size() - 1) : 0; }
|
||||
size_t recalc_equal_spacing_vertical() { return m_views.size() > 2 ? (bounds().height() - recalc_total_content_height()) / (m_views.size() - 1) : 0; }
|
||||
|
||||
std::vector<View*> m_views;
|
||||
size_t m_spacing { 0 };
|
||||
Alignment m_alignment { Alignment::Leading };
|
||||
Distribution m_distribution { Distribution::Standard };
|
||||
LayoutConstraints::Axis m_axis { LayoutConstraints::Axis::Horizontal };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
60
libs/libui/include/libui/TextField.h
Normal file
60
libs/libui/include/libui/TextField.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Constants/Text.h>
|
||||
#include <libui/Control.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <string>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class TextField : public Control {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
System,
|
||||
Custom,
|
||||
};
|
||||
|
||||
~TextField() = default;
|
||||
|
||||
const std::string& text() const { return m_text; }
|
||||
void set_text(const std::string& text) { m_text = text, set_needs_display(); }
|
||||
void set_text(std::string&& text) { m_text = std::move(text), set_needs_display(); }
|
||||
|
||||
void set_placeholder_text(const std::string& text) { m_placeholder_text = text, set_needs_display(); }
|
||||
void set_placeholder_text(std::string&& text) { m_placeholder_text = std::move(text), set_needs_display(); }
|
||||
|
||||
void set_text_color(const LG::Color& color) { m_text_color = color; }
|
||||
const LG::Color& text_color() const { return m_text_color; }
|
||||
|
||||
void set_content_edge_insets(const EdgeInsets& ei) { m_content_edge_insets = ei; }
|
||||
const EdgeInsets& content_edge_insets() const { return m_content_edge_insets; }
|
||||
|
||||
void set_font(const LG::Font& font) { m_font = font, set_needs_display(); }
|
||||
inline const LG::Font& font() const { return m_font; }
|
||||
|
||||
virtual void display(const LG::Rect& rect) override;
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override;
|
||||
virtual void mouse_exited() override;
|
||||
|
||||
virtual void mouse_down(const LG::Point<int>& location) override;
|
||||
virtual void mouse_up() override;
|
||||
|
||||
protected:
|
||||
TextField(View* superview, const LG::Rect& frame);
|
||||
|
||||
private:
|
||||
size_t text_width(const std::string& text);
|
||||
size_t text_height(const std::string& text) const;
|
||||
|
||||
std::string m_text {};
|
||||
std::string m_placeholder_text {};
|
||||
LG::Color m_text_color { LG::Color::DarkSystemText };
|
||||
LG::Font m_font { LG::Font::system_font() };
|
||||
|
||||
Text::Alignment m_alignment { Text::Alignment::Left };
|
||||
EdgeInsets m_content_edge_insets { 12, 12, 12, 12 };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
43
libs/libui/include/libui/TextView.h
Normal file
43
libs/libui/include/libui/TextView.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <libg/Size.h>
|
||||
#include <libui/Constants/Layout.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/ScrollView.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class TextView : public ScrollView {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
~TextView() = default;
|
||||
|
||||
void set_text(const std::string& text) { m_text = text, recalc_text_size(), set_needs_display(); }
|
||||
void set_text(std::string&& text) { m_text = std::move(text), recalc_text_size(), set_needs_display(); }
|
||||
const std::string& text() const { return m_text; }
|
||||
|
||||
void set_text_color(const LG::Color& color) { m_text_color = color, set_needs_display(); }
|
||||
const LG::Color& text_color() const { return m_text_color; }
|
||||
|
||||
void set_font(const LG::Font& font) { m_font = font, recalc_text_size(), set_needs_display(); }
|
||||
inline const LG::Font& font() const { return m_font; }
|
||||
|
||||
virtual void display(const LG::Rect& rect) override;
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override;
|
||||
virtual void mouse_exited() override;
|
||||
|
||||
protected:
|
||||
TextView(View* superview, const LG::Rect&);
|
||||
TextView(View* superview, Window* window, const LG::Rect& frame);
|
||||
|
||||
private:
|
||||
void recalc_text_size();
|
||||
|
||||
std::string m_text {};
|
||||
LG::Color m_text_color { LG::Color::Black };
|
||||
LG::Font m_font { LG::Font::system_font() };
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
277
libs/libui/include/libui/View.h
Normal file
277
libs/libui/include/libui/View.h
Normal file
@@ -0,0 +1,277 @@
|
||||
#pragma once
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Point.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <libui/Constraint.h>
|
||||
#include <libui/ContextManager.h>
|
||||
#include <libui/EdgeInsets.h>
|
||||
#include <libui/GestureManager.h>
|
||||
#include <libui/Layer.h>
|
||||
#include <libui/Responder.h>
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define UI_OBJECT() friend class View
|
||||
|
||||
namespace UI {
|
||||
|
||||
struct SafeArea {
|
||||
static const int Top = 8;
|
||||
#ifdef TARGET_DESKTOP
|
||||
static const int Bottom = 8;
|
||||
#elif TARGET_MOBILE
|
||||
static const int Bottom = 20;
|
||||
#endif
|
||||
static const int Left = 8;
|
||||
static const int Right = 8;
|
||||
};
|
||||
|
||||
struct Padding {
|
||||
static const int System = 8;
|
||||
static const int AfterTitle = 12;
|
||||
};
|
||||
|
||||
class Window;
|
||||
|
||||
class View : public Responder {
|
||||
public:
|
||||
friend class Window;
|
||||
|
||||
~View();
|
||||
|
||||
template <class T, class... Args>
|
||||
T& add_subview(Args&&... args)
|
||||
{
|
||||
T* subview = new T(this, std::forward<Args>(args)...);
|
||||
m_subviews.push_back(subview);
|
||||
did_add_subview(*subview);
|
||||
return *subview;
|
||||
}
|
||||
virtual void did_add_subview(View& view) { }
|
||||
|
||||
void remove_from_superview();
|
||||
|
||||
template <typename Callback>
|
||||
void foreach_subview(Callback callback) const
|
||||
{
|
||||
for (auto* viewptr : m_subviews) {
|
||||
if (!callback(*viewptr)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::optional<View*> subview_at(const LG::Point<int>& point) const;
|
||||
View& hit_test(const LG::Point<int>& point);
|
||||
|
||||
inline const LG::Rect& frame() const { return m_frame; }
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
inline LG::Rect& frame() { return m_frame; }
|
||||
inline LG::Rect& bounds() { return m_bounds; }
|
||||
inline LG::Point<int> center() { return LG::Point<int>(frame().mid_x(), frame().mid_y()); }
|
||||
inline void set_width(size_t x) { m_frame.set_width(x), m_bounds.set_width(x), set_needs_display(); }
|
||||
inline void set_height(size_t x) { m_frame.set_height(x), m_bounds.set_height(x), set_needs_display(); }
|
||||
|
||||
inline void turn_on_constraint_based_layout(bool b) { m_constraint_based_layout = b; }
|
||||
void add_constraint(const Constraint& constraint) { m_constrints.push_back(constraint); }
|
||||
const std::vector<UI::Constraint>& constraints() const { return m_constrints; }
|
||||
|
||||
template <class RecognizerT, class ActionType>
|
||||
inline void add_gesture_recognizer(ActionType m_target)
|
||||
{
|
||||
RecognizerT* recognizer = new RecognizerT(m_target);
|
||||
m_gesture_manager.add(recognizer);
|
||||
}
|
||||
|
||||
virtual void layout_subviews();
|
||||
inline void set_needs_layout()
|
||||
{
|
||||
send_layout_message(*window(), this);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
LG::Rect frame_in_window();
|
||||
|
||||
inline Window* window() { return m_window; }
|
||||
inline bool has_superview() { return m_superview; }
|
||||
inline View* superview() { return m_superview; }
|
||||
inline std::list<View*>& subviews() { return m_subviews; }
|
||||
inline const std::list<View*>& subviews() const { return m_subviews; }
|
||||
|
||||
void set_needs_display(const LG::Rect&);
|
||||
inline void set_needs_display() { set_needs_display(bounds()); }
|
||||
|
||||
inline bool is_hovered() const { return m_hovered; }
|
||||
inline bool is_active() const { return m_active; }
|
||||
|
||||
inline void set_focusable(bool val) { m_focusable = val; }
|
||||
inline bool is_focusable() const { return m_focusable; }
|
||||
|
||||
inline Layer& layer() { return m_layer; }
|
||||
inline const Layer& layer() const { return m_layer; }
|
||||
|
||||
virtual void display(const LG::Rect& rect);
|
||||
virtual void did_display(const LG::Rect& rect);
|
||||
|
||||
virtual void mouse_moved(const LG::Point<int>& new_location);
|
||||
virtual void mouse_entered(const LG::Point<int>& location);
|
||||
virtual void mouse_exited();
|
||||
|
||||
// Return
|
||||
enum WheelEventResponse {
|
||||
Skipped,
|
||||
Handled,
|
||||
};
|
||||
virtual WheelEventResponse mouse_wheel_event(int wheel_data);
|
||||
|
||||
virtual void mouse_down(const LG::Point<int>& location);
|
||||
virtual void mouse_up();
|
||||
|
||||
virtual void receive_mouse_move_event(MouseEvent&) override;
|
||||
virtual void receive_mouse_action_event(MouseActionEvent&) override;
|
||||
virtual void receive_mouse_leave_event(MouseLeaveEvent&) override;
|
||||
virtual bool receive_mouse_wheel_event(MouseWheelEvent&) override;
|
||||
virtual void receive_keyup_event(KeyUpEvent&) override;
|
||||
virtual void receive_keydown_event(KeyDownEvent&) override;
|
||||
virtual void receive_display_event(DisplayEvent&) override;
|
||||
virtual bool receive_layout_event(const LayoutEvent&, bool force_layout_if_not_target = false) override;
|
||||
|
||||
inline LG::Color& background_color() { return m_background_color; }
|
||||
inline const LG::Color& background_color() const { return m_background_color; }
|
||||
inline void set_background_color(const LG::Color& background_color) { m_background_color = background_color, set_needs_display(); }
|
||||
|
||||
protected:
|
||||
View(View* superview, const LG::Rect&);
|
||||
View(View* superview, Window* window, const LG::Rect&);
|
||||
|
||||
inline void set_hovered(bool value) { m_hovered = value; }
|
||||
inline void set_active(bool value) { m_active = value; }
|
||||
|
||||
virtual LG::Point<int> subview_location(const View& subview) const;
|
||||
|
||||
template <Constraint::Attribute attr>
|
||||
inline void add_interpreted_constraint_to_mask() { m_applied_constraints_mask |= (1 << (int)attr); }
|
||||
|
||||
template <Constraint::Attribute attr>
|
||||
inline bool has_interpreted_constraint_in_mask() { return (m_applied_constraints_mask & (1 << (int)attr)); }
|
||||
|
||||
inline void constraint_interpreter(const Constraint& constraint);
|
||||
|
||||
private:
|
||||
void set_window(Window* window) { m_window = window; }
|
||||
void set_superview(View* superview) { m_superview = superview; }
|
||||
void remove_view(View* view);
|
||||
|
||||
View* m_superview { nullptr };
|
||||
Window* m_window { nullptr };
|
||||
std::list<View*> m_subviews;
|
||||
LG::Rect m_frame;
|
||||
LG::Rect m_bounds;
|
||||
|
||||
bool m_constraint_based_layout { false };
|
||||
std::vector<Constraint> m_constrints {};
|
||||
uint32_t m_applied_constraints_mask { 0 }; // Constraints applied to this view;
|
||||
|
||||
bool m_active { false };
|
||||
bool m_hovered { false };
|
||||
bool m_focusable { false };
|
||||
|
||||
LG::Color m_background_color { LG::Color::White };
|
||||
Layer m_layer {};
|
||||
|
||||
GestureManager m_gesture_manager;
|
||||
};
|
||||
|
||||
inline void View::constraint_interpreter(const Constraint& constraint)
|
||||
{
|
||||
auto get_rel_item_attribute = [&]() {
|
||||
// Constraints could be added between a view and the view's superview or another view with the same superview.
|
||||
//
|
||||
// If it's the case view and the view's superview, it has to take attributes from bound to calculate the right
|
||||
// view's posistion within this superview.
|
||||
if (constraint.rel_item() == constraint.item()->superview()) {
|
||||
return Constraint::get_attribute<int>(constraint.rel_item()->bounds(), constraint.rel_attribute());
|
||||
} else {
|
||||
return Constraint::get_attribute<int>(constraint.rel_item()->frame(), constraint.rel_attribute());
|
||||
}
|
||||
};
|
||||
|
||||
auto calc_new_value = [&]() {
|
||||
return constraint.multiplier() * get_rel_item_attribute() + constraint.constant();
|
||||
};
|
||||
|
||||
switch (constraint.attribute()) {
|
||||
case Constraint::Attribute::Top:
|
||||
Constraint::set_attribute<Constraint::Attribute::Top>(constraint.item()->frame(), calc_new_value());
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::Top>();
|
||||
return;
|
||||
|
||||
case Constraint::Attribute::Bottom: {
|
||||
uint32_t value = calc_new_value();
|
||||
if (!constraint.rel_item()) {
|
||||
value = constraint.item()->superview()->bounds().max_y() - constraint.constant();
|
||||
}
|
||||
|
||||
if (constraint.item()->has_interpreted_constraint_in_mask<Constraint::Attribute::Top>()) {
|
||||
uint32_t res = value - constraint.item()->frame().min_y();
|
||||
Constraint::set_attribute<Constraint::Attribute::Height>(constraint.item()->frame(), res);
|
||||
Constraint::set_attribute<Constraint::Attribute::Height>(constraint.item()->bounds(), res);
|
||||
} else {
|
||||
Constraint::set_attribute<Constraint::Attribute::Bottom>(constraint.item()->frame(), value);
|
||||
}
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::Bottom>();
|
||||
return;
|
||||
}
|
||||
case Constraint::Attribute::Left:
|
||||
Constraint::set_attribute<Constraint::Attribute::Left>(constraint.item()->frame(), calc_new_value());
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::Left>();
|
||||
return;
|
||||
|
||||
case Constraint::Attribute::Right: {
|
||||
uint32_t value = calc_new_value();
|
||||
if (!constraint.rel_item()) {
|
||||
value = constraint.item()->superview()->bounds().max_x() - constraint.constant();
|
||||
}
|
||||
|
||||
if (constraint.item()->has_interpreted_constraint_in_mask<Constraint::Attribute::Left>()) {
|
||||
uint32_t res = value - constraint.item()->frame().min_x();
|
||||
Constraint::set_attribute<Constraint::Attribute::Width>(constraint.item()->frame(), res);
|
||||
Constraint::set_attribute<Constraint::Attribute::Width>(constraint.item()->bounds(), res);
|
||||
} else {
|
||||
Constraint::set_attribute<Constraint::Attribute::Right>(constraint.item()->frame(), value);
|
||||
}
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::Right>();
|
||||
return;
|
||||
}
|
||||
|
||||
case Constraint::Attribute::CenterX:
|
||||
Constraint::set_attribute<Constraint::Attribute::CenterX>(constraint.item()->frame(), calc_new_value());
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::CenterX>();
|
||||
return;
|
||||
|
||||
case Constraint::Attribute::CenterY:
|
||||
Constraint::set_attribute<Constraint::Attribute::CenterY>(constraint.item()->frame(), calc_new_value());
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::CenterY>();
|
||||
return;
|
||||
|
||||
case Constraint::Attribute::Width:
|
||||
Constraint::set_attribute<Constraint::Attribute::Width>(constraint.item()->frame(), calc_new_value());
|
||||
Constraint::set_attribute<Constraint::Attribute::Width>(constraint.item()->bounds(), calc_new_value());
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::Width>();
|
||||
return;
|
||||
|
||||
case Constraint::Attribute::Height:
|
||||
Constraint::set_attribute<Constraint::Attribute::Height>(constraint.item()->frame(), calc_new_value());
|
||||
Constraint::set_attribute<Constraint::Attribute::Height>(constraint.item()->bounds(), calc_new_value());
|
||||
constraint.item()->add_interpreted_constraint_to_mask<Constraint::Attribute::Height>();
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
47
libs/libui/include/libui/ViewController.h
Normal file
47
libs/libui/include/libui/ViewController.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libui/View.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class BaseViewController : public LFoundation::EventReceiver {
|
||||
public:
|
||||
BaseViewController() = default;
|
||||
virtual ~BaseViewController() = default;
|
||||
|
||||
virtual void view_did_load() { }
|
||||
virtual void receive_event(std::unique_ptr<LFoundation::Event> event) override
|
||||
{
|
||||
switch (event->type()) {
|
||||
case Event::Type::ViewDidLoad:
|
||||
view_did_load();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
template <class ViewT>
|
||||
class ViewController : public BaseViewController {
|
||||
public:
|
||||
ViewController(ViewT& view)
|
||||
: m_view(view)
|
||||
{
|
||||
}
|
||||
virtual ~ViewController() = default;
|
||||
|
||||
ViewT& view() { return m_view; }
|
||||
const ViewT& view() const { return m_view; }
|
||||
|
||||
Window& window() { return *m_view.window(); }
|
||||
|
||||
private:
|
||||
ViewT& m_view;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
104
libs/libui/include/libui/Window.h
Normal file
104
libs/libui/include/libui/Window.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include <libapi/window_server/Connections/WSConnection.h>
|
||||
#include <libapi/window_server/MessageContent/MenuBar.h>
|
||||
#include <libapi/window_server/MessageContent/Window.h>
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libfoundation/SharedBuffer.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Size.h>
|
||||
#include <libui/MenuBar.h>
|
||||
#include <libui/PopupMenu.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/ViewController.h>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
using ::WindowStatusUpdateType;
|
||||
using ::WindowType;
|
||||
|
||||
class Connection;
|
||||
|
||||
class Window : public LFoundation::EventReceiver {
|
||||
UI_OBJECT();
|
||||
friend Connection;
|
||||
|
||||
public:
|
||||
Window(const std::string& title, const LG::Size& size, WindowType type = WindowType::Standard);
|
||||
Window(const std::string& title, const LG::Size& size, const std::string& path);
|
||||
Window(const std::string& title, const LG::Size& size, const std::string& path, const StatusBarStyle& style);
|
||||
|
||||
int id() const { return m_id; }
|
||||
inline WindowType type() const { return m_type; }
|
||||
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
LFoundation::SharedBuffer<LG::Color>& buffer() { return m_buffer; }
|
||||
const LFoundation::SharedBuffer<LG::Color>& buffer() const { return m_buffer; }
|
||||
|
||||
LG::PixelBitmap& bitmap() { return m_bitmap; }
|
||||
const LG::PixelBitmap& bitmap() const { return m_bitmap; }
|
||||
inline void set_bitmap_format(LG::PixelBitmapFormat format) { m_bitmap.set_format(format), did_format_change(); }
|
||||
|
||||
template <class ViewT, class ViewControllerT, class... Args>
|
||||
inline ViewT& create_superview(Args&&... args)
|
||||
{
|
||||
ViewT* new_view = new ViewT(nullptr, this, bounds(), args...);
|
||||
m_superview = new_view;
|
||||
m_root_view_controller = new ViewControllerT(*new_view);
|
||||
|
||||
setup_superview();
|
||||
m_superview->set_needs_display();
|
||||
LFoundation::EventLoop::the().add(*m_root_view_controller, new ViewDidLoadEvent());
|
||||
return *new_view;
|
||||
}
|
||||
inline View* superview() const { return m_superview; }
|
||||
|
||||
inline void set_focused_view(View& view) { m_focused_view = &view; }
|
||||
inline View* focused_view() { return m_focused_view; }
|
||||
|
||||
MenuBar& menubar() { return m_menubar; }
|
||||
PopupMenu& popup_manager() { return m_popup; }
|
||||
|
||||
bool set_title(const std::string& title);
|
||||
bool set_status_bar_style(StatusBarStyle style);
|
||||
bool did_format_change();
|
||||
bool did_buffer_change();
|
||||
|
||||
inline const std::string& title() const { return m_title; }
|
||||
inline const std::string& icon_path() const { return m_icon_path; }
|
||||
inline const StatusBarStyle& status_bar_style() const { return m_status_bar_style; }
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
|
||||
private:
|
||||
void init_window(const std::string& title, const LG::Size& size, const std::string& icon_path, const StatusBarStyle& style, WindowType type);
|
||||
void resize(ResizeEvent&);
|
||||
void setup_superview();
|
||||
void fill_with_opaque(const LG::Rect&);
|
||||
|
||||
inline const LG::Rect& native_bounds() const { return m_native_bounds; }
|
||||
|
||||
uint32_t m_id;
|
||||
BaseViewController* m_root_view_controller { nullptr };
|
||||
View* m_superview { nullptr };
|
||||
View* m_focused_view { nullptr };
|
||||
|
||||
LG::Rect m_bounds;
|
||||
LG::Rect m_native_bounds;
|
||||
LG::PixelBitmap m_bitmap;
|
||||
LFoundation::SharedBuffer<LG::Color> m_buffer;
|
||||
std::string m_title { "" };
|
||||
std::string m_icon_path { "/res/icons/apps/missing.icon" };
|
||||
LG::Color m_color;
|
||||
StatusBarStyle m_status_bar_style;
|
||||
WindowType m_type { WindowType::Standard };
|
||||
int m_scale { 1 };
|
||||
|
||||
MenuBar m_menubar;
|
||||
PopupMenu m_popup;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
31
libs/libui/src/App.cpp
Normal file
31
libs/libui/src/App.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include <libipc/DoubleSidedConnection.h>
|
||||
#include <libui/App.h>
|
||||
#include <memory>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
App* s_UI_App_the = nullptr;
|
||||
|
||||
App::App()
|
||||
: m_event_loop()
|
||||
, m_server_connection(LIPC::DoubleSidedConnection(socket(PF_LOCAL, 0, 0), socket(PF_LOCAL, 0, 0)))
|
||||
{
|
||||
s_UI_App_the = this;
|
||||
}
|
||||
|
||||
void App::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case Event::Type::WindowCloseRequestEvent: {
|
||||
// TODO: Only 1 window is supported for now
|
||||
WindowCloseRequestEvent& own_event = *(WindowCloseRequestEvent*)event.get();
|
||||
auto message = DestroyWindowMessage(m_server_connection.key(), own_event.window_id());
|
||||
auto reply = m_server_connection.send_sync_message<DestroyWindowMessageReply>(message);
|
||||
m_event_loop.stop(reply->status());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
96
libs/libui/src/Button.cpp
Normal file
96
libs/libui/src/Button.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libui/Button.h>
|
||||
#include <libui/Context.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
Button::Button(View* superview, const LG::Rect& frame)
|
||||
: Control(superview, frame)
|
||||
{
|
||||
layer().set_corner_mask(LG::CornerMask(4));
|
||||
set_background_color(LG::Color::LightSystemButton);
|
||||
}
|
||||
|
||||
void Button::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context& ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
ctx.set_fill_color(background_color());
|
||||
if (m_button_type == Type::System) {
|
||||
if (is_hovered()) {
|
||||
ctx.set_fill_color(background_color().darken(5));
|
||||
}
|
||||
}
|
||||
ctx.fill_rounded(bounds(), layer().corner_mask());
|
||||
|
||||
size_t content_width = text_width();
|
||||
size_t content_height = text_height();
|
||||
LG::Point<int> text_start { content_edge_insets().left(), std::max(content_edge_insets().top(), int(bounds().height() - content_height) / 2) };
|
||||
if (alignment() == Text::Alignment::Center) {
|
||||
text_start.set_x((bounds().width() - content_width) / 2);
|
||||
} else if (alignment() == Text::Alignment::Right) {
|
||||
text_start.set_x(bounds().width() - content_width);
|
||||
}
|
||||
|
||||
auto& f = font();
|
||||
ctx.set_fill_color(title_color());
|
||||
for (int i = 0; i < m_title.size(); i++) {
|
||||
auto& glyph = f.glyph(m_title[i]);
|
||||
ctx.draw(text_start, glyph);
|
||||
text_start.offset_by(glyph.advance(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::mouse_entered(const LG::Point<int>& location)
|
||||
{
|
||||
send_actions(UI::Event::Type::MouseEnterEvent);
|
||||
View::mouse_entered(location);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void Button::mouse_exited()
|
||||
{
|
||||
send_actions(UI::Event::Type::MouseLeaveEvent);
|
||||
View::mouse_exited();
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void Button::mouse_down(const LG::Point<int>& location)
|
||||
{
|
||||
send_actions(UI::Event::Type::MouseDownEvent);
|
||||
View::mouse_down(location);
|
||||
}
|
||||
|
||||
void Button::mouse_up()
|
||||
{
|
||||
send_actions(UI::Event::Type::MouseUpEvent);
|
||||
View::mouse_up();
|
||||
}
|
||||
|
||||
void Button::recalc_bounds()
|
||||
{
|
||||
size_t new_width = text_width() + content_edge_insets().left() + content_edge_insets().right();
|
||||
size_t new_height = text_height() + content_edge_insets().top() + content_edge_insets().bottom();
|
||||
set_width(new_width);
|
||||
set_height(new_height);
|
||||
}
|
||||
|
||||
size_t Button::text_width()
|
||||
{
|
||||
size_t width = 0;
|
||||
auto& f = font();
|
||||
|
||||
for (int i = 0; i < m_title.size(); i++) {
|
||||
auto& glyph = f.glyph(m_title[i]);
|
||||
width += glyph.advance();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
size_t Button::text_height() const
|
||||
{
|
||||
return font().size();
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
131
libs/libui/src/ClientDecoder.cpp
Normal file
131
libs/libui/src/ClientDecoder.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <libui/App.h>
|
||||
#include <libui/ClientDecoder.h>
|
||||
#include <libui/Event.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
ClientDecoder::ClientDecoder()
|
||||
: m_event_loop(LFoundation::EventLoop::the())
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(MouseMoveMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new MouseEvent(msg.x(), msg.y()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(MouseActionMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new MouseActionEvent((MouseActionType)msg.type(), msg.x(), msg.y()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(MouseLeaveMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new MouseLeaveEvent(msg.x(), msg.y()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(MouseWheelMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new MouseWheelEvent(msg.x(), msg.y(), msg.wheel_data()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(KeyboardMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
// Checking if the key is down or up
|
||||
if (msg.kbd_key() >> 31) {
|
||||
uint32_t key = msg.kbd_key();
|
||||
key &= 0x7FFFFFFF;
|
||||
m_event_loop.add(App::the().window(), new KeyUpEvent(key));
|
||||
} else {
|
||||
m_event_loop.add(App::the().window(), new KeyDownEvent(msg.kbd_key()));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(DisplayMessage& msg)
|
||||
{
|
||||
m_event_loop.add(App::the().window(), new DisplayEvent(msg.rect()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(WindowCloseRequestMessage& msg)
|
||||
{
|
||||
// TODO: Currently we support only 1 window per app.
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the(), new WindowCloseRequestEvent(msg.win_id()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(ResizeMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new ResizeEvent(msg.win_id(), msg.rect()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(MenuBarActionMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new MenuBarActionEvent(msg.win_id(), msg.menu_id(), msg.item_id()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(PopupActionMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new PopupActionEvent(msg.win_id(), msg.menu_id(), msg.item_id()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Notifiers
|
||||
std::unique_ptr<Message> ClientDecoder::handle(NotifyWindowCreateMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new NotifyWindowCreateEvent(msg.bundle_id().move_string(), msg.icon_path().move_string(), msg.changed_window_id(), msg.changed_window_type()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(NotifyWindowStatusChangedMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new NotifyWindowStatusChangedEvent(msg.changed_window_id(), msg.type()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(NotifyWindowIconChangedMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new NotifyWindowIconChangedEvent(msg.changed_window_id(), msg.icon_path().move_string()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> ClientDecoder::handle(NotifyWindowTitleChangedMessage& msg)
|
||||
{
|
||||
if (App::the().window().id() == msg.win_id()) {
|
||||
m_event_loop.add(App::the().window(), new NotifyWindowTitleChangedEvent(msg.changed_window_id(), msg.title().move_string()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
180
libs/libui/src/CollectionView.cpp
Normal file
180
libs/libui/src/CollectionView.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include <libg/Color.h>
|
||||
#include <libui/CollectionView.h>
|
||||
#include <libui/Context.h>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
CollectionView::CollectionView(View* superview, const LG::Rect& frame)
|
||||
: ScrollView(superview, frame)
|
||||
{
|
||||
}
|
||||
|
||||
CollectionView::CollectionView(View* superview, Window* window, const LG::Rect& frame)
|
||||
: ScrollView(superview, window, frame)
|
||||
{
|
||||
}
|
||||
|
||||
void CollectionView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
display_scroll_indicators(ctx);
|
||||
}
|
||||
|
||||
void CollectionView::mouse_entered(const LG::Point<int>& location)
|
||||
{
|
||||
set_hovered(true);
|
||||
}
|
||||
|
||||
void CollectionView::mouse_exited()
|
||||
{
|
||||
set_hovered(false);
|
||||
}
|
||||
|
||||
void CollectionView::invalidate_row(int rowid)
|
||||
{
|
||||
if (!m_data_source) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto remove_view_from_list = [this, rowid](std::list<View*>& lst, size_t view_offset) {
|
||||
View* view = m_data_source((int)rowid);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = lst.begin();
|
||||
std::advance(it, view_offset);
|
||||
|
||||
View* prev_view = *it;
|
||||
view->frame().set_y(prev_view->frame().min_y());
|
||||
view->set_needs_layout();
|
||||
|
||||
lst.insert(it, view);
|
||||
lst.erase(it);
|
||||
|
||||
prev_view->remove_from_superview();
|
||||
delete prev_view;
|
||||
};
|
||||
|
||||
if (m_first_onscreen_row_index <= rowid && rowid < m_first_onscreen_row_index + m_views_on_screen.size()) {
|
||||
remove_view_from_list(m_views_on_screen, rowid - m_first_onscreen_row_index);
|
||||
} else if (m_first_offscreen_row_index <= rowid && rowid < m_first_offscreen_row_index + m_following_views.size()) {
|
||||
remove_view_from_list(m_following_views, rowid - m_first_offscreen_row_index);
|
||||
} else if (m_first_onscreen_row_index - m_preceding_views.size() <= rowid && rowid < m_first_onscreen_row_index) {
|
||||
remove_view_from_list(m_preceding_views, rowid - (m_first_onscreen_row_index - m_preceding_views.size()));
|
||||
}
|
||||
}
|
||||
|
||||
CollectionView::PrefetchStatus CollectionView::prefetch_row_forward(int id)
|
||||
{
|
||||
size_t cached_lines = m_first_offscreen_row_index + m_following_views.size();
|
||||
if (id < cached_lines) {
|
||||
return PrefetchStatus::Success;
|
||||
}
|
||||
|
||||
// See comment on CollectionViewRowStreamer
|
||||
View* view = m_data_source((int)id);
|
||||
if (!view) {
|
||||
return PrefetchStatus::EndOfStream;
|
||||
}
|
||||
|
||||
m_following_views.push_back(view);
|
||||
view->frame().set_y(m_next_frame_origin.y());
|
||||
view->set_needs_layout();
|
||||
|
||||
int next_y = m_next_frame_origin.y() + view->bounds().height();
|
||||
m_next_frame_origin.set_y(next_y);
|
||||
content_size().set_height(next_y);
|
||||
|
||||
return PrefetchStatus::Success;
|
||||
}
|
||||
|
||||
void CollectionView::prefetch_forward()
|
||||
{
|
||||
if (!m_data_source) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not initialized, so prefetching while data is on screen.
|
||||
if (content_size().height() < bounds().height()) {
|
||||
for (;; m_first_offscreen_row_index++) {
|
||||
PrefetchStatus full_line_prefetched = prefetch_row_forward(m_first_offscreen_row_index);
|
||||
if (full_line_prefetched == PrefetchStatus::EndOfStream) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_views_on_screen.push_back(m_following_views.front());
|
||||
m_following_views.pop_front();
|
||||
|
||||
if (!bounds().contains(m_next_frame_origin)) {
|
||||
m_first_offscreen_row_index++;
|
||||
// Exiting here since the next frame is out of screen.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int cache_depth = 4;
|
||||
for (int i = 0; i < cache_depth; i++) {
|
||||
PrefetchStatus full_line_prefetched = prefetch_row_forward(m_first_offscreen_row_index + i);
|
||||
if (full_line_prefetched == PrefetchStatus::EndOfStream) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollectionView::after_scroll_backward()
|
||||
{
|
||||
|
||||
if (!m_preceding_views.empty()) {
|
||||
View* back_view = m_preceding_views.back();
|
||||
auto back_view_frame = back_view->frame();
|
||||
back_view_frame.offset_by(-content_offset());
|
||||
if (bounds().intersects(back_view_frame)) {
|
||||
m_views_on_screen.push_front(m_preceding_views.back());
|
||||
m_preceding_views.pop_back();
|
||||
m_first_onscreen_row_index--;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_views_on_screen.empty()) {
|
||||
View* back_view = m_views_on_screen.back();
|
||||
auto back_view_frame = back_view->frame();
|
||||
back_view_frame.offset_by(-content_offset());
|
||||
if (!bounds().intersects(back_view_frame)) {
|
||||
m_following_views.push_front(m_views_on_screen.back());
|
||||
m_views_on_screen.pop_back();
|
||||
m_first_offscreen_row_index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollectionView::after_scroll_forward()
|
||||
{
|
||||
if (!m_views_on_screen.empty()) {
|
||||
View* front_view = m_views_on_screen.front();
|
||||
auto front_view_frame = front_view->frame();
|
||||
front_view_frame.offset_by(-content_offset());
|
||||
if (!bounds().intersects(front_view_frame)) {
|
||||
m_preceding_views.push_back(m_views_on_screen.front());
|
||||
m_views_on_screen.pop_front();
|
||||
m_first_onscreen_row_index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_following_views.empty()) {
|
||||
View* front_view = m_following_views.front();
|
||||
auto front_view_frame = front_view->frame();
|
||||
front_view_frame.offset_by(-content_offset());
|
||||
if (bounds().intersects(front_view_frame)) {
|
||||
m_views_on_screen.push_back(m_following_views.front());
|
||||
m_following_views.pop_front();
|
||||
m_first_offscreen_row_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
105
libs/libui/src/Connection.cpp
Normal file
105
libs/libui/src/Connection.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libfoundation/ProcessInfo.h>
|
||||
#include <libipc/ClientConnection.h>
|
||||
#include <libui/Connection.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <sched.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
// #define DEBUG_CONNECTION
|
||||
|
||||
namespace UI {
|
||||
|
||||
#define WINSERVER_REQUEST_SOCKET_PATH "/tmp/winserver_requests.sock"
|
||||
#define WINSERVER_REQUEST_SOCKET_PATH_SIZE sizeof(WINSERVER_REQUEST_SOCKET_PATH)
|
||||
|
||||
#define WINSERVER_RESPONSE_SOCKET_PATH "/tmp/winserver_response.sock"
|
||||
#define WINSERVER_RESPONSE_SOCKET_PATH_SIZE sizeof(WINSERVER_RESPONSE_SOCKET_PATH)
|
||||
|
||||
static Connection* s_the = nullptr;
|
||||
|
||||
Connection& Connection::the()
|
||||
{
|
||||
// FIXME: Thread-safe method to be applied
|
||||
if (!s_the) {
|
||||
auto conn = LIPC::DoubleSidedConnection(socket(PF_LOCAL, 0, 0), socket(PF_LOCAL, 0, 0));
|
||||
new Connection(conn);
|
||||
}
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
Connection::Connection(const LIPC::DoubleSidedConnection& connection)
|
||||
: m_connection(connection)
|
||||
, m_server_decoder()
|
||||
, m_client_decoder()
|
||||
, m_connection_with_server(m_connection, m_server_decoder, m_client_decoder)
|
||||
{
|
||||
s_the = this;
|
||||
if (m_connection.c2s_fd() > 0 && m_connection.s2c_fd() > 0) {
|
||||
bool req_connected = false;
|
||||
bool resp_connected = false;
|
||||
// Trying to connect for 100 times. If unsuccesfull, it crashes.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (!req_connected) {
|
||||
if (connect(m_connection.c2s_fd(), WINSERVER_REQUEST_SOCKET_PATH, WINSERVER_REQUEST_SOCKET_PATH_SIZE) == 0) {
|
||||
req_connected = true;
|
||||
}
|
||||
}
|
||||
if (!resp_connected) {
|
||||
if (connect(m_connection.s2c_fd(), WINSERVER_RESPONSE_SOCKET_PATH, WINSERVER_RESPONSE_SOCKET_PATH_SIZE) == 0) {
|
||||
resp_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (req_connected && resp_connected) {
|
||||
break;
|
||||
}
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
if (!req_connected || !resp_connected) {
|
||||
goto crash;
|
||||
}
|
||||
|
||||
greeting();
|
||||
setup_listners();
|
||||
return;
|
||||
}
|
||||
crash:
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void Connection::setup_listners()
|
||||
{
|
||||
LFoundation::EventLoop::the().add(
|
||||
m_connection.s2c_fd(), [] {
|
||||
Connection::the().listen();
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void Connection::greeting()
|
||||
{
|
||||
auto resp_message = send_sync_message<GreetMessageReply>(GreetMessage(getpid()));
|
||||
m_connection_id = resp_message->connection_id();
|
||||
m_connection_with_server.set_accepted_key(m_connection_id);
|
||||
#ifdef DEBUG_CONNECTION
|
||||
Logger::debug << "Got greet with server" << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
int Connection::new_window(const Window& window)
|
||||
{
|
||||
const std::string& bundle_id = LFoundation::ProcessInfo::the().bundle_id();
|
||||
auto message = CreateWindowMessage(key(), window.type(), window.native_bounds().width(), window.native_bounds().height(),
|
||||
window.buffer().id(), window.title(), window.icon_path(), bundle_id,
|
||||
window.status_bar_style().color().u32(), window.status_bar_style().flags());
|
||||
auto resp_message = send_sync_message<CreateWindowMessageReply>(message);
|
||||
#ifdef DEBUG_CONNECTION
|
||||
Logger::debug << "New window created" << std::endl;
|
||||
#endif
|
||||
return resp_message->window_id();
|
||||
}
|
||||
} // namespace UI
|
||||
6
libs/libui/src/ContextManager.cpp
Normal file
6
libs/libui/src/ContextManager.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <libui/ContextManager.h>
|
||||
#include <vector>
|
||||
|
||||
namespace UI {
|
||||
std::vector<LG::Context> s_ui_graphics_contexts;
|
||||
}
|
||||
86
libs/libui/src/Label.cpp
Normal file
86
libs/libui/src/Label.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/Label.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
Label::Label(View* superview, const LG::Rect& frame)
|
||||
: View(superview, frame)
|
||||
{
|
||||
}
|
||||
|
||||
Label::Label(View* superview, Window* window, const LG::Rect& frame)
|
||||
: View(superview, window, frame)
|
||||
{
|
||||
}
|
||||
|
||||
void Label::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
auto& f = font();
|
||||
size_t label_width = bounds().width() - content_edge_insets().left() - content_edge_insets().right();
|
||||
size_t txt_width = text_width();
|
||||
size_t dot_width = f.glyph('.').width();
|
||||
size_t dots_width = f.glyph('.').advance();
|
||||
|
||||
bool need_to_stop_rendering_text = (txt_width > label_width);
|
||||
size_t width_when_stop_rendering_text = content_edge_insets().left() + label_width - dots_width;
|
||||
|
||||
size_t content_width = text_width();
|
||||
size_t content_height = text_height();
|
||||
LG::Point<int> text_start { content_edge_insets().left(), std::max(content_edge_insets().top(), int(bounds().height() - content_height) / 2) };
|
||||
if (alignment() == Text::Alignment::Center) {
|
||||
int preffered_start = ((int)bounds().width() - (int)content_width) / 2;
|
||||
text_start.set_x(std::max(content_edge_insets().left(), preffered_start));
|
||||
} else if (alignment() == Text::Alignment::Right) {
|
||||
int preffered_start = (int)bounds().width() - (int)content_width;
|
||||
text_start.set_x(std::max(content_edge_insets().left(), preffered_start));
|
||||
}
|
||||
|
||||
ctx.set_fill_color(text_color());
|
||||
for (int i = 0; i < m_text.size(); i++) {
|
||||
size_t glyph_advance = f.glyph(m_text[i]).advance();
|
||||
if (need_to_stop_rendering_text && text_start.x() + glyph_advance > width_when_stop_rendering_text) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
ctx.draw(text_start, f.glyph('.'));
|
||||
text_start.offset_by(f.glyph('.').advance(), 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ctx.draw(text_start, f.glyph(m_text[i]));
|
||||
text_start.offset_by(glyph_advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::recalc_bounds()
|
||||
{
|
||||
size_t new_width = text_width() + content_edge_insets().left() + content_edge_insets().right();
|
||||
size_t new_height = text_height() + content_edge_insets().top() + content_edge_insets().bottom();
|
||||
set_width(new_width);
|
||||
set_height(new_height);
|
||||
}
|
||||
|
||||
size_t Label::text_width() const
|
||||
{
|
||||
if (!m_text.size()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t width = 0;
|
||||
auto& f = font();
|
||||
|
||||
for (int i = 0; i < m_text.size(); i++) {
|
||||
width += f.glyph(m_text[i]).advance();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
size_t Label::text_height() const
|
||||
{
|
||||
return font().size();
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
37
libs/libui/src/MenuBar.cpp
Normal file
37
libs/libui/src/MenuBar.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/MenuBar.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
void MenuBar::add_menu(const Menu& menu)
|
||||
{
|
||||
Menu new_menu = menu;
|
||||
auto& connection = App::the().connection();
|
||||
auto resp_message = connection.send_sync_message<MenuBarCreateMenuMessageReply>(MenuBarCreateMenuMessage(connection.key(), m_host_window_id, new_menu.title()));
|
||||
new_menu.set_menu_id(resp_message->menu_id());
|
||||
|
||||
// TODO: Speed up sending only 1 message.
|
||||
for (auto& item : new_menu.items()) {
|
||||
auto resp_message = connection.send_sync_message<MenuBarCreateItemMessageReply>(MenuBarCreateItemMessage(connection.key(), host_window_id(), new_menu.menu_id(), item.m_id, item.title()));
|
||||
}
|
||||
|
||||
m_menus.push_back(std::move(new_menu));
|
||||
}
|
||||
|
||||
void MenuBar::add_menu(Menu&& menu)
|
||||
{
|
||||
Menu new_menu = std::move(menu);
|
||||
auto& connection = App::the().connection();
|
||||
auto resp_message = connection.send_sync_message<MenuBarCreateMenuMessageReply>(MenuBarCreateMenuMessage(connection.key(), m_host_window_id, new_menu.title()));
|
||||
new_menu.set_menu_id(resp_message->menu_id());
|
||||
|
||||
// TODO: Speed up sending only 1 message.
|
||||
for (auto& item : new_menu.items()) {
|
||||
auto resp_message = connection.send_sync_message<MenuBarCreateItemMessageReply>(MenuBarCreateItemMessage(connection.key(), host_window_id(), new_menu.menu_id(), item.m_id, item.title()));
|
||||
}
|
||||
|
||||
m_menus.push_back(std::move(new_menu));
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
35
libs/libui/src/PopupMenu.cpp
Normal file
35
libs/libui/src/PopupMenu.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libg/Point.h>
|
||||
#include <libipc/VectorEncoder.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/PopupMenu.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
void PopupMenu::show(LG::Point<int> point, const Menu& menu)
|
||||
{
|
||||
m_menu = menu;
|
||||
auto& connection = App::the().connection();
|
||||
|
||||
std::vector<LIPC::StringEncoder> tmp;
|
||||
for (auto& item : m_menu.items()) {
|
||||
tmp.push_back(item.title());
|
||||
}
|
||||
|
||||
auto resp_message = connection.send_sync_message<PopupShowMenuMessageReply>(PopupShowMenuMessage(connection.key(), m_host_window_id, point, LIPC::VectorEncoder<LIPC::StringEncoder>(std::move(tmp))));
|
||||
}
|
||||
|
||||
void PopupMenu::show(LG::Point<int> point, Menu&& menu)
|
||||
{
|
||||
m_menu = std::move(menu);
|
||||
auto& connection = App::the().connection();
|
||||
|
||||
std::vector<LIPC::StringEncoder> tmp;
|
||||
for (auto& item : m_menu.items()) {
|
||||
tmp.push_back(item.title());
|
||||
}
|
||||
|
||||
auto resp_message = connection.send_sync_message<PopupShowMenuMessageReply>(PopupShowMenuMessage(connection.key(), m_host_window_id, point, LIPC::VectorEncoder<LIPC::StringEncoder>(std::move(tmp))));
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
53
libs/libui/src/Responder.cpp
Normal file
53
libs/libui/src/Responder.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/Responder.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
bool Responder::send_invalidate_message_to_server(const LG::Rect& rect) const
|
||||
{
|
||||
auto& app = App::the();
|
||||
InvalidateMessage msg(Connection::the().key(), app.window().id(), rect);
|
||||
return app.connection().send_async_message(msg);
|
||||
}
|
||||
|
||||
void Responder::send_layout_message(Window& win, UI::View* for_view)
|
||||
{
|
||||
LFoundation::EventLoop::the().add(win, new LayoutEvent(for_view));
|
||||
m_display_message_sent = false;
|
||||
}
|
||||
|
||||
void Responder::send_display_message_to_self(Window& win, const LG::Rect& display_rect)
|
||||
{
|
||||
if (!m_display_message_sent || m_prev_display_message != display_rect) {
|
||||
LFoundation::EventLoop::the().add(win, new DisplayEvent(display_rect));
|
||||
m_display_message_sent = true;
|
||||
m_prev_display_message = display_rect;
|
||||
}
|
||||
}
|
||||
|
||||
void Responder::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case Event::Type::MouseEvent: {
|
||||
MouseEvent& own_event = *(MouseEvent*)event.get();
|
||||
receive_mouse_move_event(own_event);
|
||||
break;
|
||||
}
|
||||
|
||||
case Event::Type::DisplayEvent: {
|
||||
DisplayEvent& own_event = *(DisplayEvent*)event.get();
|
||||
receive_display_event(own_event);
|
||||
break;
|
||||
}
|
||||
|
||||
case Event::Type::LayoutEvent: {
|
||||
LayoutEvent& own_event = *(LayoutEvent*)event.get();
|
||||
receive_layout_event(own_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
213
libs/libui/src/ScrollView.cpp
Normal file
213
libs/libui/src/ScrollView.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <libfoundation/Math.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/ScrollView.h>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
ScrollView::ScrollView(View* superview, const LG::Rect& frame)
|
||||
: View(superview, frame)
|
||||
{
|
||||
}
|
||||
|
||||
ScrollView::ScrollView(View* superview, Window* window, const LG::Rect& frame)
|
||||
: View(superview, window, frame)
|
||||
{
|
||||
}
|
||||
|
||||
void ScrollView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
display_scroll_indicators(ctx);
|
||||
}
|
||||
|
||||
void ScrollView::do_scroll_animation_step()
|
||||
{
|
||||
int diff = m_animation_target.y() - m_content_offset.y();
|
||||
int abs_diff = abs(diff);
|
||||
m_scroll_velocity = LFoundation::fast_sqrt(abs_diff);
|
||||
m_scroll_velocity *= (((abs_diff / 8)) + 1);
|
||||
if (diff < 0) {
|
||||
m_scroll_velocity = -m_scroll_velocity;
|
||||
}
|
||||
do_scroll(0, m_scroll_velocity);
|
||||
}
|
||||
|
||||
void ScrollView::rearm_scroll_animation()
|
||||
{
|
||||
LFoundation::EventLoop::the().add(LFoundation::Timer([this] {
|
||||
this->do_scroll_animation_step();
|
||||
if (m_content_offset != m_animation_target) {
|
||||
this->rearm_scroll_animation();
|
||||
} else {
|
||||
m_has_timer = false;
|
||||
}
|
||||
},
|
||||
1000 / 60));
|
||||
}
|
||||
|
||||
void ScrollView::do_scroll(int n_x, int n_y)
|
||||
{
|
||||
int x = content_offset().x();
|
||||
int y = content_offset().y();
|
||||
int max_x = std::max(0, (int)content_size().width() - (int)bounds().width());
|
||||
int max_y = std::max(0, (int)content_size().height() - (int)bounds().height());
|
||||
content_offset().set_x(std::max(0, std::min(x + n_x, max_x)));
|
||||
content_offset().set_y(std::max(0, std::min(y + n_y, max_y)));
|
||||
auto me = MouseEvent(m_mouse_location.x(), m_mouse_location.y());
|
||||
receive_mouse_move_event(me);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void ScrollView::setup_scroll_animation(int wheel_data)
|
||||
{
|
||||
auto get_sign = [](int x) -> int {
|
||||
return (x > 0) ? 1 : ((x < 0) ? -1 : 0);
|
||||
};
|
||||
|
||||
int dist = abs(m_animation_target.y() - m_content_offset.y());
|
||||
if (dist < 10 || get_sign(wheel_data) != get_sign(m_last_scroll_multiplier)) {
|
||||
m_last_scroll_multiplier = 10;
|
||||
} else if (dist > 30) {
|
||||
m_last_scroll_multiplier = 20;
|
||||
} else {
|
||||
m_last_scroll_multiplier = abs(m_last_scroll_multiplier);
|
||||
if (m_last_scroll_multiplier > dist) {
|
||||
m_last_scroll_multiplier--;
|
||||
} else {
|
||||
m_last_scroll_multiplier++;
|
||||
}
|
||||
}
|
||||
|
||||
if (wheel_data < 0) {
|
||||
m_last_scroll_multiplier = -m_last_scroll_multiplier;
|
||||
}
|
||||
|
||||
int n_x = 0;
|
||||
int n_y = m_last_scroll_multiplier;
|
||||
|
||||
int x = m_animation_target.x();
|
||||
int y = m_animation_target.y();
|
||||
int max_x = std::max(0, (int)content_size().width() - (int)bounds().width());
|
||||
int max_y = std::max(0, (int)content_size().height() - (int)bounds().height());
|
||||
m_animation_target.set_x(std::max(0, std::min(x + n_x, max_x)));
|
||||
m_animation_target.set_y(std::max(0, std::min(y + n_y, max_y)));
|
||||
|
||||
if (!m_has_timer) {
|
||||
m_has_timer = true;
|
||||
rearm_scroll_animation();
|
||||
}
|
||||
}
|
||||
|
||||
View::WheelEventResponse ScrollView::mouse_wheel_event(int wheel_data)
|
||||
{
|
||||
setup_scroll_animation(wheel_data);
|
||||
return View::WheelEventResponse::Handled;
|
||||
}
|
||||
|
||||
LG::Point<int> ScrollView::subview_location(const View& subview) const
|
||||
{
|
||||
auto frame_origin = subview.frame().origin();
|
||||
frame_origin.offset_by(-m_content_offset);
|
||||
return frame_origin;
|
||||
}
|
||||
|
||||
std::optional<View*> ScrollView::subview_at(const LG::Point<int>& point) const
|
||||
{
|
||||
for (auto it = subviews().rbegin(); it != subviews().rend(); it++) {
|
||||
View* view = *it;
|
||||
auto frame = view->frame();
|
||||
frame.offset_by(-m_content_offset);
|
||||
if (frame.contains(point)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ScrollView::receive_mouse_move_event(MouseEvent& event)
|
||||
{
|
||||
m_mouse_location = LG::Point<int>(event.x(), event.y());
|
||||
if (!is_hovered()) {
|
||||
mouse_entered(m_mouse_location);
|
||||
}
|
||||
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
auto frame = subview.frame();
|
||||
frame.offset_by(-m_content_offset);
|
||||
bool event_hits_subview = frame.contains(event.x(), event.y());
|
||||
if (subview.is_hovered() && !event_hits_subview) {
|
||||
LG::Point<int> point(event.x(), event.y());
|
||||
point.offset_by(-frame.origin());
|
||||
MouseLeaveEvent mle(point.x(), point.y());
|
||||
subview.receive_mouse_leave_event(mle);
|
||||
} else if (event_hits_subview) {
|
||||
LG::Point<int> point(event.x(), event.y());
|
||||
point.offset_by(-frame.origin());
|
||||
MouseEvent me(point.x(), point.y());
|
||||
subview.receive_mouse_move_event(me);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
mouse_moved(m_mouse_location);
|
||||
Responder::receive_mouse_move_event(event);
|
||||
}
|
||||
|
||||
void ScrollView::receive_display_event(DisplayEvent& event)
|
||||
{
|
||||
event.bounds().intersect(bounds());
|
||||
display(event.bounds());
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
auto bounds = event.bounds();
|
||||
auto frame = subview.frame();
|
||||
frame.offset_by(-m_content_offset);
|
||||
bounds.intersect(frame);
|
||||
if (!bounds.empty()) {
|
||||
subview.layer().display(bounds, frame);
|
||||
graphics_push_context(Context(subview, frame, Context::RelativeToCurrentContext::Yes));
|
||||
bounds.origin().offset_by(-frame.origin());
|
||||
DisplayEvent own_event(bounds);
|
||||
subview.receive_display_event(own_event);
|
||||
graphics_pop_context();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
did_display(event.bounds());
|
||||
|
||||
if (!has_superview()) {
|
||||
// Only superview sends invalidate_message to server.
|
||||
bool success = send_invalidate_message_to_server(event.bounds());
|
||||
}
|
||||
|
||||
Responder::receive_display_event(event);
|
||||
}
|
||||
|
||||
void ScrollView::recalc_content_props()
|
||||
{
|
||||
int max_width = 0;
|
||||
int max_height = 0;
|
||||
|
||||
for (auto* view : subviews()) {
|
||||
max_width = std::max(max_width, view->frame().max_x());
|
||||
max_height = std::max(max_height, view->frame().max_y());
|
||||
}
|
||||
|
||||
m_content_size.set_width(max_width);
|
||||
m_content_size.set_height(max_height);
|
||||
}
|
||||
|
||||
void ScrollView::display_scroll_indicators(LG::Context& ctx)
|
||||
{
|
||||
float ratio = (float)bounds().height() / (float)content_size().height();
|
||||
int line_height = bounds().height() * ratio;
|
||||
int start_y = content_offset().y() * ratio;
|
||||
int start_x = bounds().max_x() - 6;
|
||||
ctx.set_fill_color(LG::Color(0x6AAAF4));
|
||||
ctx.fill_rounded(LG::Rect(start_x, start_y, 4, line_height), LG::CornerMask(2));
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
158
libs/libui/src/StackView.cpp
Normal file
158
libs/libui/src/StackView.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/StackView.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
StackView::StackView(View* superview, const LG::Rect& frame)
|
||||
: View(superview, frame)
|
||||
{
|
||||
}
|
||||
|
||||
StackView::StackView(View* superview, Window* window, const LG::Rect& frame)
|
||||
: View(superview, window, frame)
|
||||
{
|
||||
}
|
||||
|
||||
bool StackView::receive_layout_event(const LayoutEvent& event, bool force_layout_if_not_target)
|
||||
{
|
||||
// StackView uses receive_layout_event to recalculate positions of
|
||||
// subviews.
|
||||
bool res = View::receive_layout_event(event, force_layout_if_not_target);
|
||||
if (this == event.target() || force_layout_if_not_target) {
|
||||
recalc_subviews_positions();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t StackView::recalc_subview_min_x(View* view)
|
||||
{
|
||||
size_t width = bounds().width();
|
||||
switch (alignment()) {
|
||||
case Alignment::Leading:
|
||||
return 0;
|
||||
case Alignment::Center:
|
||||
return (width - view->bounds().width()) / 2;
|
||||
case Alignment::Trailing:
|
||||
return width - view->bounds().width();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t StackView::recalc_subview_min_y(View* view)
|
||||
{
|
||||
size_t height = bounds().height();
|
||||
switch (alignment()) {
|
||||
case Alignment::Leading:
|
||||
return 0;
|
||||
case Alignment::Center:
|
||||
return (height - view->bounds().height()) / 2;
|
||||
case Alignment::Trailing:
|
||||
return height - view->bounds().height();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StackView::recalc_fill_equally()
|
||||
{
|
||||
// TODO: May be to reinterpret contstraints here?
|
||||
size_t total_spacing = spacing() * (m_views.size() - 1);
|
||||
if (axis() == LayoutConstraints::Axis::Horizontal) {
|
||||
size_t width = (bounds().width() - total_spacing) / m_views.size();
|
||||
for (int i = 0; i < m_views.size(); i++) {
|
||||
constraint_interpreter(Constraint(*m_views[i], Constraint::Attribute::Width, Constraint::Relation::Equal, width));
|
||||
}
|
||||
} else {
|
||||
size_t height = (bounds().height() - total_spacing) / m_views.size();
|
||||
for (int i = 0; i < m_views.size(); i++) {
|
||||
constraint_interpreter(Constraint(*m_views[i], Constraint::Attribute::Height, Constraint::Relation::Equal, height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t StackView::recalc_total_content_width()
|
||||
{
|
||||
size_t res = 0;
|
||||
for (int i = 0; i < m_views.size(); i++) {
|
||||
res += m_views[i]->frame().width();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t StackView::recalc_total_content_height()
|
||||
{
|
||||
size_t res = 0;
|
||||
for (int i = 0; i < m_views.size(); i++) {
|
||||
res += m_views[i]->frame().height();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t StackView::recalc_initial_spacing(size_t element_spacing)
|
||||
{
|
||||
size_t total_spacing = element_spacing * (m_views.size() - 1);
|
||||
|
||||
switch (distribution()) {
|
||||
case Distribution::EqualCentering:
|
||||
if (axis() == LayoutConstraints::Axis::Horizontal) {
|
||||
int content_width = recalc_total_content_width() + total_spacing;
|
||||
return (bounds().width() - content_width) / 2;
|
||||
} else {
|
||||
int content_height = recalc_total_content_height() + total_spacing;
|
||||
return (bounds().height() - content_height) / 2;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t StackView::recalc_spacing()
|
||||
{
|
||||
switch (distribution()) {
|
||||
case Distribution::Standard:
|
||||
case Distribution::EqualCentering:
|
||||
return spacing();
|
||||
case Distribution::EqualSpacing:
|
||||
if (axis() == LayoutConstraints::Axis::Horizontal) {
|
||||
return recalc_equal_spacing_horizontal();
|
||||
} else {
|
||||
return recalc_equal_spacing_vertical();
|
||||
}
|
||||
case Distribution::FillEqually:
|
||||
recalc_fill_equally();
|
||||
return spacing();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// recalc_subviews_positions recalculates the posistion of all subviews.
|
||||
// You have to call set_needs_layout instread of direct call to recalc_subviews_positions.
|
||||
void StackView::recalc_subviews_positions()
|
||||
{
|
||||
size_t spacing = recalc_spacing();
|
||||
size_t initial_spacing = recalc_initial_spacing(spacing);
|
||||
size_t primary_coord = initial_spacing;
|
||||
if (axis() == LayoutConstraints::Axis::Horizontal) {
|
||||
for (int i = 0; i < m_views.size(); i++) {
|
||||
m_views[i]->frame().set_y(recalc_subview_min_y(m_views[i]));
|
||||
m_views[i]->frame().set_x(primary_coord);
|
||||
primary_coord += m_views[i]->frame().width() + spacing;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < m_views.size(); i++) {
|
||||
m_views[i]->frame().set_x(recalc_subview_min_x(m_views[i]));
|
||||
m_views[i]->frame().set_y(primary_coord);
|
||||
primary_coord += m_views[i]->frame().height() + spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
77
libs/libui/src/TextField.cpp
Normal file
77
libs/libui/src/TextField.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/TextField.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
TextField::TextField(View* superview, const LG::Rect& frame)
|
||||
: Control(superview, frame)
|
||||
{
|
||||
layer().set_corner_mask(LG::CornerMask(4));
|
||||
set_background_color(LG::Color::LightSystemButton);
|
||||
}
|
||||
|
||||
void TextField::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context& ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
ctx.set_fill_color(background_color());
|
||||
ctx.fill_rounded(bounds(), layer().corner_mask());
|
||||
|
||||
std::string* display_text_ptr;
|
||||
if (m_text.empty()) {
|
||||
display_text_ptr = &m_placeholder_text;
|
||||
ctx.set_fill_color(LG::Color(120, 120, 120));
|
||||
} else {
|
||||
display_text_ptr = &m_text;
|
||||
ctx.set_fill_color(text_color());
|
||||
}
|
||||
const std::string& display_text = *display_text_ptr;
|
||||
size_t content_width = text_width(display_text);
|
||||
size_t content_height = text_height(display_text);
|
||||
int right_offset_of_last_glyph = std::min(content_edge_insets().left() + content_width, bounds().width() - content_edge_insets().left() - content_edge_insets().right());
|
||||
LG::Point<int> last_glyph_start { right_offset_of_last_glyph, std::max(content_edge_insets().top(), int(bounds().height() - content_height) / 2) };
|
||||
|
||||
auto& f = font();
|
||||
for (int i = display_text.size() - 1; i >= 0; i--) {
|
||||
auto& glyph = f.glyph(display_text[i]);
|
||||
last_glyph_start.offset_by(-glyph.advance(), 0);
|
||||
ctx.draw(last_glyph_start, glyph);
|
||||
}
|
||||
}
|
||||
|
||||
void TextField::mouse_entered(const LG::Point<int>& location)
|
||||
{
|
||||
}
|
||||
|
||||
void TextField::mouse_exited()
|
||||
{
|
||||
}
|
||||
|
||||
void TextField::mouse_down(const LG::Point<int>& location)
|
||||
{
|
||||
}
|
||||
|
||||
void TextField::mouse_up()
|
||||
{
|
||||
}
|
||||
|
||||
size_t TextField::text_width(const std::string& text)
|
||||
{
|
||||
size_t width = 0;
|
||||
auto& f = font();
|
||||
|
||||
for (int i = 0; i < text.size(); i++) {
|
||||
auto& glyph = f.glyph(text[i]);
|
||||
width += glyph.advance();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
size_t TextField::text_height(const std::string& text) const
|
||||
{
|
||||
return font().size();
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
83
libs/libui/src/TextView.cpp
Normal file
83
libs/libui/src/TextView.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/TextView.h>
|
||||
#include <utility>
|
||||
|
||||
namespace UI {
|
||||
|
||||
TextView::TextView(View* superview, const LG::Rect& frame)
|
||||
: ScrollView(superview, frame)
|
||||
{
|
||||
}
|
||||
|
||||
TextView::TextView(View* superview, Window* window, const LG::Rect& frame)
|
||||
: ScrollView(superview, window, frame)
|
||||
{
|
||||
}
|
||||
|
||||
void TextView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
auto& f = font();
|
||||
const size_t line_height = f.size();
|
||||
|
||||
int start_x = -content_offset().x();
|
||||
int start_y = -content_offset().y();
|
||||
int cur_x = start_x;
|
||||
int cur_y = start_y;
|
||||
|
||||
for (int i = 0; i < m_text.size(); i++) {
|
||||
if (m_text[i] == '\n') {
|
||||
cur_x = start_x;
|
||||
cur_y += line_height;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t glyph_advance = f.glyph(m_text[i]).advance();
|
||||
if (bounds().contains(cur_x, cur_y) || bounds().contains(cur_x + glyph_advance, cur_y + line_height)) {
|
||||
ctx.draw({ cur_x, cur_y }, f.glyph(m_text[i]));
|
||||
}
|
||||
cur_x += glyph_advance;
|
||||
}
|
||||
|
||||
display_scroll_indicators(ctx);
|
||||
}
|
||||
|
||||
void TextView::mouse_entered(const LG::Point<int>& location)
|
||||
{
|
||||
set_hovered(true);
|
||||
}
|
||||
|
||||
void TextView::mouse_exited()
|
||||
{
|
||||
set_hovered(false);
|
||||
}
|
||||
|
||||
void TextView::recalc_text_size()
|
||||
{
|
||||
auto& f = font();
|
||||
const size_t line_height = f.size();
|
||||
|
||||
int cur_x = 0;
|
||||
int max_x = 0;
|
||||
int cur_y = 0;
|
||||
|
||||
for (int i = 0; i < m_text.size(); i++) {
|
||||
if (m_text[i] == '\n') {
|
||||
std::max(max_x, cur_x);
|
||||
cur_x = 0;
|
||||
cur_y += line_height;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t glyph_width = f.glyph(m_text[i]).advance();
|
||||
cur_x += glyph_width;
|
||||
}
|
||||
|
||||
std::max(max_x, cur_x);
|
||||
content_size().set(LG::Size(cur_x, cur_y + line_height));
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
288
libs/libui/src/View.cpp
Normal file
288
libs/libui/src/View.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/View.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
View::View(View* superview, const LG::Rect& frame)
|
||||
: m_frame(frame)
|
||||
, m_superview(superview)
|
||||
, m_window(superview ? superview->window() : nullptr)
|
||||
, m_bounds(0, 0, frame.width(), frame.height())
|
||||
, m_layer()
|
||||
{
|
||||
}
|
||||
|
||||
View::View(View* superview, Window* window, const LG::Rect& frame)
|
||||
: m_frame(frame)
|
||||
, m_superview(superview)
|
||||
, m_window(window)
|
||||
, m_bounds(0, 0, frame.width(), frame.height())
|
||||
, m_layer()
|
||||
{
|
||||
}
|
||||
|
||||
View::~View()
|
||||
{
|
||||
for (auto* v : subviews()) {
|
||||
delete v;
|
||||
}
|
||||
}
|
||||
|
||||
void View::remove_view(View* child)
|
||||
{
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::find(m_subviews.begin(), m_subviews.end(), child);
|
||||
if (it == m_subviews.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_subviews.erase(it);
|
||||
}
|
||||
|
||||
void View::remove_from_superview()
|
||||
{
|
||||
if (!superview()) {
|
||||
return;
|
||||
}
|
||||
superview()->remove_view(this);
|
||||
}
|
||||
|
||||
std::optional<View*> View::subview_at(const LG::Point<int>& point) const
|
||||
{
|
||||
for (auto it = subviews().rbegin(); it != subviews().rend(); it++) {
|
||||
View* view = *it;
|
||||
if (view->frame().contains(point)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
View& View::hit_test(const LG::Point<int>& point)
|
||||
{
|
||||
auto subview = subview_at(point);
|
||||
if (subview.has_value()) {
|
||||
return subview.value()->hit_test(point - subview_location(*subview.value()));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LG::Rect View::frame_in_window()
|
||||
{
|
||||
View* view = this;
|
||||
auto rect = frame();
|
||||
while (view->has_superview()) {
|
||||
view = view->superview();
|
||||
rect.offset_by(view->frame().origin());
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
void View::layout_subviews()
|
||||
{
|
||||
// TODO: Apply topsort to find the right order.
|
||||
for (const auto& constraint : m_constrints) {
|
||||
constraint_interpreter(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
LG::Point<int> View::subview_location(const View& subview) const
|
||||
{
|
||||
return subview.frame().origin();
|
||||
}
|
||||
|
||||
void View::set_needs_display(const LG::Rect& rect)
|
||||
{
|
||||
auto display_rect = rect;
|
||||
display_rect.intersect(bounds());
|
||||
if (has_superview()) {
|
||||
auto location = superview()->subview_location(*this);
|
||||
display_rect.offset_by(location);
|
||||
superview()->set_needs_display(display_rect);
|
||||
} else {
|
||||
send_display_message_to_self(*window(), display_rect);
|
||||
}
|
||||
}
|
||||
|
||||
void View::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
ctx.set_fill_color(background_color());
|
||||
ctx.fill_rounded(bounds(), layer().corner_mask());
|
||||
}
|
||||
|
||||
void View::did_display(const LG::Rect& rect)
|
||||
{
|
||||
}
|
||||
|
||||
void View::mouse_moved(const LG::Point<int>& location)
|
||||
{
|
||||
m_gesture_manager.mouse_moved(location);
|
||||
}
|
||||
|
||||
void View::mouse_entered(const LG::Point<int>& location)
|
||||
{
|
||||
set_hovered(true);
|
||||
}
|
||||
|
||||
void View::mouse_exited()
|
||||
{
|
||||
set_hovered(false);
|
||||
set_active(false);
|
||||
m_gesture_manager.mouse_up();
|
||||
}
|
||||
|
||||
void View::mouse_down(const LG::Point<int>& location)
|
||||
{
|
||||
set_active(true);
|
||||
m_gesture_manager.mouse_down(location);
|
||||
}
|
||||
|
||||
void View::mouse_up()
|
||||
{
|
||||
set_active(false);
|
||||
m_gesture_manager.mouse_up();
|
||||
}
|
||||
|
||||
View::WheelEventResponse View::mouse_wheel_event(int wheel_data)
|
||||
{
|
||||
return View::WheelEventResponse::Skipped;
|
||||
}
|
||||
|
||||
void View::receive_mouse_move_event(MouseEvent& event)
|
||||
{
|
||||
auto location = LG::Point<int>(event.x(), event.y());
|
||||
if (!is_hovered()) {
|
||||
mouse_entered(location);
|
||||
}
|
||||
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
bool event_hits_subview = subview.frame().contains(event.x(), event.y());
|
||||
if (subview.is_hovered() && !event_hits_subview) {
|
||||
LG::Point<int> point(event.x(), event.y());
|
||||
point.offset_by(-subview.frame().origin());
|
||||
MouseLeaveEvent mle(point.x(), point.y());
|
||||
subview.receive_mouse_leave_event(mle);
|
||||
} else if (event_hits_subview) {
|
||||
LG::Point<int> point(event.x(), event.y());
|
||||
point.offset_by(-subview.frame().origin());
|
||||
MouseEvent me(point.x(), point.y());
|
||||
subview.receive_mouse_move_event(me);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
mouse_moved(location);
|
||||
Responder::receive_mouse_move_event(event);
|
||||
}
|
||||
|
||||
void View::receive_mouse_action_event(MouseActionEvent& event)
|
||||
{
|
||||
if (event.type() == MouseActionType::LeftMouseButtonPressed) {
|
||||
mouse_down(LG::Point<int>(event.x(), event.y()));
|
||||
} else if (event.type() == MouseActionType::LeftMouseButtonReleased) {
|
||||
mouse_up();
|
||||
}
|
||||
|
||||
Responder::receive_mouse_action_event(event);
|
||||
}
|
||||
|
||||
void View::receive_mouse_leave_event(MouseLeaveEvent& event)
|
||||
{
|
||||
if (!is_hovered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
if (subview.is_hovered()) {
|
||||
LG::Point<int> point(event.x(), event.y());
|
||||
point.offset_by(-subview.frame().origin());
|
||||
MouseLeaveEvent mle(point.x(), point.y());
|
||||
subview.receive_mouse_leave_event(mle);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
mouse_exited();
|
||||
Responder::receive_mouse_leave_event(event);
|
||||
}
|
||||
|
||||
bool View::receive_mouse_wheel_event(MouseWheelEvent& event)
|
||||
{
|
||||
bool found_target = false;
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
if (subview.is_hovered()) {
|
||||
LG::Point<int> point(event.x(), event.y());
|
||||
point.offset_by(-subview.frame().origin());
|
||||
MouseWheelEvent mwe(point.x(), point.y(), event.wheel_data());
|
||||
found_target |= subview.receive_mouse_wheel_event(mwe);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// If not child can process wheel/scroll event, we try to do this.
|
||||
if (!found_target) {
|
||||
WheelEventResponse resp = mouse_wheel_event(event.wheel_data());
|
||||
if (resp == WheelEventResponse::Handled) {
|
||||
found_target = true;
|
||||
}
|
||||
}
|
||||
return found_target;
|
||||
}
|
||||
|
||||
void View::receive_keyup_event(KeyUpEvent&)
|
||||
{
|
||||
}
|
||||
|
||||
void View::receive_keydown_event(KeyDownEvent&)
|
||||
{
|
||||
}
|
||||
|
||||
void View::receive_display_event(DisplayEvent& event)
|
||||
{
|
||||
event.bounds().intersect(bounds());
|
||||
display(event.bounds());
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
auto bounds = event.bounds();
|
||||
if (bounds.intersects(subview.frame())) {
|
||||
subview.layer().display(bounds, subview.frame());
|
||||
graphics_push_context(Context(subview, Context::RelativeToCurrentContext::Yes));
|
||||
bounds.offset_by(-subview.frame().origin());
|
||||
DisplayEvent own_event(bounds);
|
||||
subview.receive_display_event(own_event);
|
||||
graphics_pop_context();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
did_display(event.bounds());
|
||||
|
||||
if (!has_superview()) {
|
||||
// Only superview sends invalidate_message to server.
|
||||
bool success = send_invalidate_message_to_server(event.bounds());
|
||||
}
|
||||
|
||||
Responder::receive_display_event(event);
|
||||
}
|
||||
|
||||
bool View::receive_layout_event(const LayoutEvent& event, bool force_layout_if_not_target)
|
||||
{
|
||||
bool need_to_layout = (this == event.target()) | force_layout_if_not_target;
|
||||
if (need_to_layout) {
|
||||
layout_subviews();
|
||||
}
|
||||
|
||||
foreach_subview([&](View& subview) -> bool {
|
||||
bool found_target = subview.receive_layout_event(event, need_to_layout);
|
||||
return need_to_layout || !found_target;
|
||||
});
|
||||
|
||||
return need_to_layout;
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
7
libs/libui/src/ViewController.cpp
Normal file
7
libs/libui/src/ViewController.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/ViewController.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
} // namespace UI
|
||||
222
libs/libui/src/Window.cpp
Normal file
222
libs/libui/src/Window.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#include <libfoundation/Memory.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/Connection.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/Screen.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
Window::Window(const std::string& title, const LG::Size& size, WindowType type)
|
||||
{
|
||||
init_window(title, size, "", StatusBarStyle(), type);
|
||||
}
|
||||
|
||||
Window::Window(const std::string& title, const LG::Size& size, const std::string& icon_path)
|
||||
{
|
||||
init_window(title, size, icon_path, StatusBarStyle(), WindowType::Standard);
|
||||
}
|
||||
|
||||
Window::Window(const std::string& title, const LG::Size& size, const std::string& icon_path, const StatusBarStyle& style)
|
||||
{
|
||||
init_window(title, size, icon_path, style, WindowType::Standard);
|
||||
}
|
||||
|
||||
void Window::init_window(const std::string& title, const LG::Size& size, const std::string& icon_path, const StatusBarStyle& style, WindowType type)
|
||||
{
|
||||
m_scale = UI::Screen::main().scale();
|
||||
m_bounds = LG::Rect(0, 0, size.width(), size.height());
|
||||
m_native_bounds = LG::Rect(0, 0, size.width() * m_scale, size.height() * m_scale);
|
||||
|
||||
m_buffer = LFoundation::SharedBuffer<LG::Color>(size_t(native_bounds().width() * native_bounds().height()));
|
||||
m_title = title;
|
||||
m_icon_path = icon_path;
|
||||
m_status_bar_style = style;
|
||||
m_type = type;
|
||||
|
||||
m_id = Connection::the().new_window(*this);
|
||||
m_menubar.set_host_window_id(m_id);
|
||||
m_popup.set_host_window_id(m_id);
|
||||
m_bitmap = LG::PixelBitmap(m_buffer.data(), native_bounds().width(), native_bounds().height());
|
||||
App::the().set_window(this);
|
||||
}
|
||||
|
||||
bool Window::set_title(const std::string& title)
|
||||
{
|
||||
m_title = title;
|
||||
SetTitleMessage msg(Connection::the().key(), id(), title);
|
||||
return App::the().connection().send_async_message(msg);
|
||||
}
|
||||
|
||||
bool Window::set_status_bar_style(StatusBarStyle style)
|
||||
{
|
||||
m_status_bar_style = style;
|
||||
SetBarStyleMessage msg(Connection::the().key(), id(), style.color().u32(), style.flags());
|
||||
return App::the().connection().send_async_message(msg);
|
||||
}
|
||||
|
||||
bool Window::did_buffer_change()
|
||||
{
|
||||
m_bitmap.set_data(buffer().data());
|
||||
m_bitmap.set_size({ bounds().width(), bounds().height() });
|
||||
|
||||
// If we have a superview, we also have a context for the superview.
|
||||
// This context should be updated, since the superview is resized.
|
||||
if (m_superview) {
|
||||
graphics_pop_context();
|
||||
graphics_push_context(Context(*m_superview));
|
||||
}
|
||||
|
||||
SetBufferMessage msg(Connection::the().key(), id(), buffer().id(), bitmap().format(), bounds());
|
||||
return App::the().connection().send_async_message(msg);
|
||||
}
|
||||
|
||||
bool Window::did_format_change()
|
||||
{
|
||||
if (bitmap().format() == LG::PixelBitmapFormat::RGBA) {
|
||||
// Set full bitmap as opaque, to mix colors correctly.
|
||||
fill_with_opaque(bounds());
|
||||
}
|
||||
|
||||
return did_buffer_change();
|
||||
}
|
||||
|
||||
void Window::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case Event::Type::MouseEvent:
|
||||
if (m_superview) {
|
||||
MouseEvent& own_event = *(MouseEvent*)event.get();
|
||||
m_superview->receive_mouse_move_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::MouseActionEvent:
|
||||
if (m_superview) {
|
||||
MouseActionEvent& own_event = *(MouseActionEvent*)event.get();
|
||||
auto& view = m_superview->hit_test({ (int)own_event.x(), (int)own_event.y() });
|
||||
view.receive_mouse_action_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::MouseLeaveEvent:
|
||||
if (m_superview) {
|
||||
MouseLeaveEvent& own_event = *(MouseLeaveEvent*)event.get();
|
||||
m_superview->receive_mouse_leave_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::MouseWheelEvent:
|
||||
if (m_superview) {
|
||||
MouseWheelEvent& own_event = *(MouseWheelEvent*)event.get();
|
||||
m_superview->receive_mouse_wheel_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::KeyUpEvent:
|
||||
if (m_focused_view) {
|
||||
KeyUpEvent& own_event = *(KeyUpEvent*)event.get();
|
||||
m_focused_view->receive_keyup_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::KeyDownEvent:
|
||||
if (m_focused_view) {
|
||||
KeyDownEvent& own_event = *(KeyDownEvent*)event.get();
|
||||
m_focused_view->receive_keydown_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::DisplayEvent:
|
||||
if (m_superview) {
|
||||
DisplayEvent& own_event = *(DisplayEvent*)event.get();
|
||||
|
||||
// If the window is in RGBA mode, we have to fill this rect
|
||||
// with opaque color before superview will mix it's color on
|
||||
// top of bitmap.
|
||||
if (bitmap().format() == LG::PixelBitmapFormat::RGBA) {
|
||||
fill_with_opaque(own_event.bounds());
|
||||
}
|
||||
|
||||
m_superview->receive_display_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::LayoutEvent:
|
||||
if (m_superview) {
|
||||
LayoutEvent& own_event = *(LayoutEvent*)event.get();
|
||||
m_superview->receive_layout_event(own_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event::Type::MenuBarActionEvent: {
|
||||
MenuBarActionEvent& own_event = *(MenuBarActionEvent*)event.get();
|
||||
for (Menu& menu : menubar().menus()) {
|
||||
if (menu.menu_id() == own_event.menu_id()) {
|
||||
if (own_event.item_id() < menu.items().size()) [[likely]] {
|
||||
menu.items()[own_event.item_id()].invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Event::Type::PopupActionEvent: {
|
||||
PopupActionEvent& own_event = *(PopupActionEvent*)event.get();
|
||||
auto& menu = popup_manager().menu();
|
||||
if (own_event.item_id() < menu.items().size()) [[likely]] {
|
||||
menu.items()[own_event.item_id()].invoke();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Event::Type::ResizeEvent: {
|
||||
ResizeEvent& own_event = *(ResizeEvent*)event.get();
|
||||
resize(own_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::resize(ResizeEvent& resize_event)
|
||||
{
|
||||
m_bounds = resize_event.bounds();
|
||||
|
||||
if (m_superview) [[likely]] {
|
||||
m_superview->frame() = resize_event.bounds();
|
||||
m_superview->bounds() = resize_event.bounds();
|
||||
m_superview->set_needs_layout();
|
||||
}
|
||||
|
||||
size_t new_size = resize_event.bounds().width() * resize_event.bounds().height();
|
||||
if (m_buffer.size() != new_size) [[likely]] {
|
||||
m_buffer.resize(resize_event.bounds().width() * resize_event.bounds().height());
|
||||
did_buffer_change();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::setup_superview()
|
||||
{
|
||||
graphics_push_context(Context(*m_superview));
|
||||
}
|
||||
|
||||
void Window::fill_with_opaque(const LG::Rect& rect)
|
||||
{
|
||||
const auto color = LG::Color(0, 0, 0, 0).u32();
|
||||
auto draw_bounds = rect;
|
||||
draw_bounds.intersect(bounds());
|
||||
if (draw_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_x = draw_bounds.min_x();
|
||||
int min_y = draw_bounds.min_y();
|
||||
int max_x = draw_bounds.max_x();
|
||||
int max_y = draw_bounds.max_y();
|
||||
int len_x = max_x - min_x + 1;
|
||||
for (int y = min_y; y <= max_y; y++) {
|
||||
LFoundation::fast_set((uint32_t*)&m_bitmap[y][min_x], color, len_x);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
28
libs/libui/src/main.cpp
Normal file
28
libs/libui/src/main.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <libfoundation/ProcessInfo.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/AppDelegate.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
|
||||
extern "C" bool __init_app_delegate(UI::AppDelegate** res);
|
||||
|
||||
// Libs are compiled with -ffreestanding and LLVM mangles
|
||||
// main() with this flag. Have to mark it as extern "C".
|
||||
extern "C" int main(int argc, char** argv)
|
||||
{
|
||||
auto process_info = LFoundation::ProcessInfo(argc, argv);
|
||||
auto& app = std::xos::construct<UI::App>();
|
||||
UI::AppDelegate* app_delegate = nullptr;
|
||||
int res = __init_app_delegate(&app_delegate);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
app.set_delegate(app_delegate);
|
||||
app.set_state(UI::AppState::Active);
|
||||
int status = app.run();
|
||||
app_delegate->application_will_terminate();
|
||||
return status;
|
||||
}
|
||||
Reference in New Issue
Block a user