Squash commits for public release

This commit is contained in:
2025-02-12 09:54:05 -05:00
commit 7118adc514
1108 changed files with 80873 additions and 0 deletions

38
libs/libui/BUILD.gn Normal file
View 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" ]
}
}

View 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

View 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

View 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

View 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

View 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

View 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;
};
}

View 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

View File

@@ -0,0 +1,10 @@
#pragma once
namespace UI::LayoutConstraints {
enum class Axis {
Horizontal,
Vertical
};
} // namespace UI::LayoutConstraints

View File

@@ -0,0 +1,11 @@
#pragma once
namespace UI::Text {
enum class Alignment {
Left,
Center,
Right,
};
} // namespace UI::Text

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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
View 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
View 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;
}