diff --git a/cpp-terminal/CMakeLists.txt b/cpp-terminal/CMakeLists.txt index 4823947b..84e6e353 100644 --- a/cpp-terminal/CMakeLists.txt +++ b/cpp-terminal/CMakeLists.txt @@ -15,10 +15,12 @@ set(CPP_TERMINAL_PUBLIC_HEADERS key.hpp mouse.hpp options.hpp + position.hpp prompt.hpp screen.hpp stream.hpp style.hpp + size.hpp terminal_impl.hpp terminal_initializer.hpp terminal.hpp diff --git a/cpp-terminal/cursor.cpp b/cpp-terminal/cursor.cpp index 77e1cb65..31f3b1c4 100644 --- a/cpp-terminal/cursor.cpp +++ b/cpp-terminal/cursor.cpp @@ -9,17 +9,13 @@ #include "cpp-terminal/cursor.hpp" -Term::Cursor::Cursor(const std::size_t& row, const std::size_t& column) : m_position({row, column}) {} +Term::Cursor::Cursor(const Position& position) : m_position(position) {} -std::size_t Term::Cursor::row() const { return m_position.first; } +std::size_t Term::Cursor::row() const { return m_position.row(); } -std::size_t Term::Cursor::column() const { return m_position.second; } +std::size_t Term::Cursor::column() const { return m_position.column(); } -bool Term::Cursor::empty() const { return (0 == m_position.first) && (0 == m_position.second); } - -void Term::Cursor::setRow(const std::size_t& row) { m_position.first = row; } - -void Term::Cursor::setColum(const std::size_t& column) { m_position.second = column; } +bool Term::Cursor::empty() const { return (0 == m_position.row()) && (0 == m_position.column()); } bool Term::Cursor::operator==(const Term::Cursor& cursor) const { return (this->row() == cursor.row()) && (this->column() == cursor.column()); } diff --git a/cpp-terminal/cursor.hpp b/cpp-terminal/cursor.hpp index ba4ca78f..5f778cf8 100644 --- a/cpp-terminal/cursor.hpp +++ b/cpp-terminal/cursor.hpp @@ -9,8 +9,8 @@ #pragma once -#include -#include +#include "cpp-terminal/position.hpp" + #include namespace Term @@ -20,17 +20,15 @@ class Cursor { public: Cursor() = default; - Cursor(const std::size_t& row, const std::size_t& column); + explicit Cursor(const Position& position); std::size_t row() const; std::size_t column() const; - void setRow(const std::size_t&); - void setColum(const std::size_t&); bool empty() const; bool operator==(const Term::Cursor& cursor) const; bool operator!=(const Term::Cursor& cursor) const; private: - std::pair m_position; + Position m_position; }; // returns the current cursor position (row, column) (Y, X) diff --git a/cpp-terminal/event.cpp b/cpp-terminal/event.cpp index 0c894ec3..c3fbd7da 100644 --- a/cpp-terminal/event.cpp +++ b/cpp-terminal/event.cpp @@ -235,7 +235,7 @@ void Term::Event::parse(const std::string& str) if(found != std::string::npos) { m_Type = Type::Cursor; - m_container.m_Cursor = Cursor(static_cast(std::stoi(str.substr(2, found - 2))), static_cast(std::stoi(str.substr(found + 1, str.size() - (found + 2))))); + m_container.m_Cursor = Cursor({Row(std::stoi(str.substr(2, found - 2))), Column(std::stoi(str.substr(found + 1, str.size() - (found + 2))))}); } } else if(str[0] == '\033' && str[1] == '[' && str[2] == '<') diff --git a/cpp-terminal/options.cpp b/cpp-terminal/options.cpp index 1781c33d..ae61c56b 100644 --- a/cpp-terminal/options.cpp +++ b/cpp-terminal/options.cpp @@ -13,8 +13,8 @@ Term::Options::Options(const std::initializer_list& option) : m_Options(option) { clean(); } -bool Term::Options::operator==(const Options& options) { return m_Options == options.m_Options; } -bool Term::Options::operator!=(const Options& options) { return !(m_Options == options.m_Options); } +bool Term::Options::operator==(const Options& options) const { return m_Options == options.m_Options; } +bool Term::Options::operator!=(const Options& options) const { return !(m_Options == options.m_Options); } void Term::Options::clean() { diff --git a/cpp-terminal/options.hpp b/cpp-terminal/options.hpp index 962d2f73..666e6902 100644 --- a/cpp-terminal/options.hpp +++ b/cpp-terminal/options.hpp @@ -38,8 +38,8 @@ class Options Options(const std::initializer_list& option); template explicit Options(const Args&&... args) : m_Options(std::initializer_list{args...}) { clean(); } - bool operator==(const Options& options); - bool operator!=(const Options& options); + bool operator==(const Options& options) const; + bool operator!=(const Options& options) const; bool has(const Option& option) const noexcept; private: diff --git a/cpp-terminal/position.hpp b/cpp-terminal/position.hpp new file mode 100644 index 00000000..399ce832 --- /dev/null +++ b/cpp-terminal/position.hpp @@ -0,0 +1,54 @@ +/* +* cpp-terminal +* C++ library for writing multi-platform terminal applications. +* +* SPDX-FileCopyrightText: 2019-2024 cpp-terminal +* +* SPDX-License-Identifier: MIT +*/ + +#pragma once + +#include +#include + +namespace Term +{ + +class Row +{ +public: + Row() = default; + explicit Row(const std::uint16_t& row) : m_row(row) {} + operator std::size_t() const noexcept { return m_row; } + +private: + std::uint16_t m_row{0}; +}; + +class Column +{ +public: + Column() = default; + explicit Column(const std::uint16_t& column) : m_column(column) {} + operator std::size_t() const noexcept { return m_column; } + +private: + std::uint16_t m_column{0}; +}; + +class Position +{ +public: + Position() = default; + Position(const Row& row, const Column& column) : m_row(row), m_column(column) {}; + Position(const Column& column, const Row& row) : m_row(row), m_column(column) {}; + const Row& row() const noexcept { return m_row; } + const Column& column() const noexcept { return m_column; } + +private: + Row m_row{1}; + Column m_column{1}; +}; + +} // namespace Term diff --git a/cpp-terminal/private/cursor.cpp b/cpp-terminal/private/cursor.cpp index 070d648a..a9695b52 100644 --- a/cpp-terminal/private/cursor.cpp +++ b/cpp-terminal/private/cursor.cpp @@ -29,9 +29,9 @@ Term::Cursor Term::cursor_position() if(Term::Private::in.null()) { return {}; } #if defined(_WIN32) CONSOLE_SCREEN_BUFFER_INFO inf; - if(GetConsoleScreenBufferInfo(Private::out.handle(), &inf)) return Term::Cursor(static_cast(inf.dwCursorPosition.Y + 1), static_cast(inf.dwCursorPosition.X + 1)); + if(GetConsoleScreenBufferInfo(Private::out.handle(), &inf)) return Term::Cursor({Row(inf.dwCursorPosition.Y + 1), Column(inf.dwCursorPosition.X + 1)}); else - return Term::Cursor(0, 0); + return {} #else std::string ret; std::size_t nread{0}; @@ -64,7 +64,7 @@ Term::Cursor Term::cursor_position() if(ret[0] == '\033' && ret[1] == '[' && ret[ret.size() - 1] == 'R') { std::size_t found = ret.find(';', 2); - if(found != std::string::npos) { return Cursor(std::stoi(ret.substr(2, found - 2)), std::stoi(ret.substr(found + 1, ret.size() - (found + 2)))); } + if(found != std::string::npos) { return Cursor({Row(std::stoi(ret.substr(2, found - 2))), Column(std::stoi(ret.substr(found + 1, ret.size() - (found + 2))))}); } return {}; } return {}; diff --git a/cpp-terminal/prompt.cpp b/cpp-terminal/prompt.cpp index 12cefd59..49cb5386 100644 --- a/cpp-terminal/prompt.cpp +++ b/cpp-terminal/prompt.cpp @@ -20,6 +20,7 @@ #include "cpp-terminal/screen.hpp" #include "cpp-terminal/terminal.hpp" #include "cpp-terminal/tty.hpp" +#include "position.hpp" #include @@ -357,7 +358,7 @@ std::string Term::prompt_multiline(const std::string& prompt_string, std::vector std::cout << scr.render(1, cursor.row(), term_attached) << std::flush; if(cursor.row() + scr.columns() - 1 > screen.rows()) { - cursor.setRow(static_cast(screen.rows() - (scr.columns() - 1))); + cursor = Cursor({Row(static_cast(screen.rows() - (scr.columns() - 1))), Column(cursor.column())}); std::cout << scr.render(1, cursor.row(), term_attached) << std::flush; } } diff --git a/cpp-terminal/window.cpp b/cpp-terminal/window.cpp index 6f1b5200..48336143 100644 --- a/cpp-terminal/window.cpp +++ b/cpp-terminal/window.cpp @@ -75,7 +75,7 @@ void Term::Window::set_bg(const std::size_t& column, const std::size_t& row, con void Term::Window::set_style(const std::size_t& column, const std::size_t& row, const Style& style) { m_style[index(column, row)] = style; } -void Term::Window::set_cursor_pos(const std::size_t& column, const std::size_t& row) { m_cursor = {row, column}; } +void Term::Window::set_cursor_pos(const std::size_t& column, const std::size_t& row) { m_cursor = Cursor({Row(row), Column(column)}); } void Term::Window::set_h(const std::size_t& new_h) { @@ -118,7 +118,7 @@ void Term::Window::print_str(const std::size_t& x, const std::size_t& y, const s ++xpos; } } - if(move_cursor) { m_cursor = {ypos, xpos}; } + if(move_cursor) { m_cursor = Cursor({Row(ypos), Column(xpos)}); } } void Term::Window::fill_fg(const std::size_t& x1, const std::size_t& y1, const std::size_t& x2, const std::size_t& y2, const Color& rgb) diff --git a/cpp-terminal/window.hpp b/cpp-terminal/window.hpp index 61979967..a2f5b535 100644 --- a/cpp-terminal/window.hpp +++ b/cpp-terminal/window.hpp @@ -79,7 +79,7 @@ class Window private: std::size_t index(const std::size_t& column, const std::size_t& row) const; Term::Size m_size; - Term::Cursor m_cursor{1, 1}; + Term::Cursor m_cursor; std::vector m_chars; // the characters in row first order std::vector m_fg; std::vector m_bg; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 160ccce6..b418b27e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -31,6 +31,7 @@ cppterminal_example(SOURCE args) cppterminal_example(SOURCE cin_cooked) cppterminal_example(SOURCE cin_raw) cppterminal_example(SOURCE colors) +cppterminal_example(SOURCE cursor) cppterminal_example(SOURCE cout) cppterminal_example(SOURCE events LIBRARIES Threads::Threads) cppterminal_example(SOURCE keys) diff --git a/examples/cursor.cpp b/examples/cursor.cpp new file mode 100644 index 00000000..a795a91d --- /dev/null +++ b/examples/cursor.cpp @@ -0,0 +1,69 @@ +/* +* cpp-terminal +* C++ library for writing multi-platform terminal applications. +* +* SPDX-FileCopyrightText: 2019-2024 cpp-terminal +* +* SPDX-License-Identifier: MIT +*/ + +#include "cpp-terminal/cursor.hpp" + +#include "cpp-terminal/exception.hpp" +#include "cpp-terminal/input.hpp" +#include "cpp-terminal/iostream.hpp" +#include "cpp-terminal/options.hpp" +#include "cpp-terminal/terminal.hpp" + +#include + +int main() +{ + try + { + // check if the terminal is capable of handling input + Term::terminal.setOptions(Term::Option::ClearScreen, Term::Option::NoSignalKeys, Term::Option::Cursor, Term::Option::Raw); + Term::Cursor cursor{Term::cursor_position()}; + Term::cout << "Cursor position : " << cursor.row() << " " << cursor.column() << std::endl; + + Term::cout << "Press any key ( 3 time 'q' to quit):" << std::endl; + int quit{0}; + while(quit != 3) + { + Term::Event event = Term::read_event(); + switch(event.type()) + { + case Term::Event::Type::Key: + { + Term::Key key(event); + if(key == Term::Key::q) { quit++; } + else + { + quit = 0; + Term::cout << key.name() << std::flush; + Term::cout << " " << Term::cursor_position_report() << std::flush; + } + break; + } + case Term::Event::Type::Cursor: + { + Term::Cursor cursor(event); + Term::cout << " [Column(X) : " << cursor.column() << " Row(Y) : " << cursor.row() << "]"; + break; + } + default: break; + } + } + } + catch(const Term::Exception& re) + { + Term::cerr << "cpp-terminal error: " << re.what() << std::endl; + return 2; + } + catch(...) + { + Term::cerr << "Unknown error." << std::endl; + return 1; + } + return 0; +} diff --git a/tests/events.test.cpp b/tests/events.test.cpp index 946a2dce..3459fc1d 100644 --- a/tests/events.test.cpp +++ b/tests/events.test.cpp @@ -80,12 +80,12 @@ TEST_CASE("Event with Focus") TEST_CASE("Event with Cursor") { - Term::Cursor cursor(1, 5); + Term::Cursor cursor({Term::Row(1), Term::Column(5)}); Term::Event event(cursor); CHECK(event.empty() == false); CHECK(event.get_if_screen() == nullptr); CHECK(event.get_if_focus() == nullptr); - CHECK(*event.get_if_cursor() == Term::Cursor(1, 5)); + CHECK(*event.get_if_cursor() == Term::Cursor({Term::Row(1), Term::Column(5)})); CHECK(event.get_if_key() == nullptr); CHECK(event.get_if_mouse() == nullptr); CHECK(event.get_if_copy_paste() == nullptr); @@ -94,7 +94,7 @@ TEST_CASE("Event with Cursor") CHECK(event2.empty() == false); CHECK(event.get_if_screen() == nullptr); CHECK(event2.get_if_focus() == nullptr); - CHECK(*event2.get_if_cursor() == Term::Cursor(1, 5)); + CHECK(*event2.get_if_cursor() == Term::Cursor({Term::Row(1), Term::Column(5)})); CHECK(event2.get_if_key() == nullptr); CHECK(event2.get_if_mouse() == nullptr); CHECK(event2.get_if_copy_paste() == nullptr);