diff --git a/doc/apps.md b/doc/apps.md index eebd9681f8..4d3c6574f6 100644 --- a/doc/apps.md +++ b/doc/apps.md @@ -468,23 +468,25 @@ Tiling Window Manager is a window container that organizes the workspace into mu - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/doc/settings.md b/doc/settings.md index ea50979871..198c560e0d 100644 --- a/doc/settings.md +++ b/doc/settings.md @@ -903,23 +903,25 @@ Notes - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/doc/user-interface.md b/doc/user-interface.md index 02d0a152a8..befac40b1b 100644 --- a/doc/user-interface.md +++ b/doc/user-interface.md @@ -260,6 +260,12 @@ Alt+Shift+E Equalize split ratio. Alt+Shift+F2 Set tiling window manager title using clipboard data. Alt+Shift+W Close active application. + LeftArrow Move the split grip to the left. + RightArrow Move the split grip to the right. + UpArrow Move the split grip up. + DownArrow Move the split grip down. + '-' Decrease the split grip width. + Shift+'+' Increase the split grip width. diff --git a/src/netxs/apps/tile.hpp b/src/netxs/apps/tile.hpp index 975a0dc501..63a99b1b64 100644 --- a/src/netxs/apps/tile.hpp +++ b/src/netxs/apps/tile.hpp @@ -24,6 +24,7 @@ namespace netxs::events::userland EVENT_XS( title , input::hids ), // Set window manager title using clipboard. GROUP_XS( focus , input::hids ), // Focusize prev/next pane. GROUP_XS( split , input::hids ), // Split panes. + GROUP_XS( grips , twod ), // Splitting grip modification. SUBSET_XS( focus ) { @@ -35,6 +36,11 @@ namespace netxs::events::userland EVENT_XS( vt, input::hids ), EVENT_XS( hz, input::hids ), }; + SUBSET_XS( grips ) + { + EVENT_XS( move , twod ), + EVENT_XS( resize, si32 ), + }; }; }; }; @@ -62,7 +68,9 @@ namespace netxs::app::tile X(TileSwapPanes ) \ X(TileEqualizeSplitRatio) \ X(TileSetManagerTitle ) \ - X(TileClosePane ) + X(TileClosePane ) \ + X(TileMoveGrip ) \ + X(TileResizeGrip ) struct action { @@ -276,7 +284,7 @@ namespace netxs::app::tile })) ->branch(slot::_2, what.applet); }; - auto build_node = [](auto tag, auto slot1, auto slot2, auto grip_width) + auto build_node = [](auto tag, auto slot1, auto slot2, auto grip_width, auto grip_bindings_ptr) { auto highlight_color = skin::color(tone::winfocus); auto c3 = highlight_color.bga(0x40); @@ -300,23 +308,46 @@ namespace netxs::app::tile gear.dismiss(); } }; + boss.LISTEN(tier::preview, app::tile::events::ui::grips::move, delta) + { + if (delta) + { + auto [orientation, griparea, ratio] = boss.get_config(); + auto step = orientation == axis::X ? delta.x : delta.y; + if (step == 0) boss.bell::expire(tier::preview, true); + else boss.move_slider(step); + } + }; + boss.LISTEN(tier::preview, app::tile::events::ui::grips::resize, step) + { + if (step) + { + auto [orientation, griparea, ratio] = boss.get_config(); + auto grip_width = orientation == axis::X ? griparea.size.x : griparea.size.y; + boss.set_grip_width(grip_width + step); + } + }; }); - auto grip = node->attach(slot::_I, - ui::mock::ctor() - ->isroot(true) - ->template plugin() //todo GCC 11 requires template keyword - ->template plugin(pro::focus::mode::focusable) - ->shader(c3, e2::form::state::focus::count) - ->template plugin>() - ->invoke([&](auto& boss) - { - boss.LISTEN(tier::release, hids::events::mouse::button::click::right, gear) - { - boss.base::riseup(tier::release, e2::form::size::minimize, gear); - gear.dismiss(); - }; - }) - ->active()); + auto grip = node->attach(slot::_I, ui::mock::ctor()) + ->isroot(true) + ->active() + ->template plugin() //todo GCC 11 requires template keyword + ->template plugin(pro::focus::mode::focusable) + ->template plugin() + ->shader(c3, e2::form::state::focus::count) + ->template plugin>() + ->invoke([&](auto& boss) + { + boss.LISTEN(tier::release, hids::events::mouse::button::click::right, gear) + { + boss.base::riseup(tier::release, e2::form::size::minimize, gear); + gear.dismiss(); + }; + auto& keybd = boss.template plugins(); + keybd.proc(action::TileMoveGrip , [&](hids& gear, txts& args){ gear.set_handled(); boss.base::riseup(tier::preview, app::tile::events::ui::grips::move, { args.size() ? xml::take_or(args.front(), dot_00) : dot_00 }); }); + keybd.proc(action::TileResizeGrip, [&](hids& gear, txts& args){ gear.set_handled(); boss.base::riseup(tier::preview, app::tile::events::ui::grips::resize, { args.size() ? xml::take_or(args.front(), 0) : 0 }); }); + keybd.bind(*grip_bindings_ptr); + }); return node; }; auto empty_slot = [] @@ -338,6 +369,7 @@ namespace netxs::app::tile { boss.LISTEN(tier::release, hids::events::mouse::button::click::left, gear) { + pro::focus::set(boss.This(), gear.id, solo::on); boss.base::riseup(tier::request, e2::form::proceed::createby, gear); gear.dismiss(true); }; @@ -392,6 +424,7 @@ namespace netxs::app::tile mouse_subs(boss); boss.LISTEN(tier::release, hids::events::mouse::button::click::right, gear) { + pro::focus::set(boss.This(), gear.id, solo::on); boss.base::riseup(tier::request, e2::form::proceed::createby, gear); gear.dismiss(true); }; @@ -407,7 +440,7 @@ namespace netxs::app::tile menu_block->alignment({ snap::head, snap::head }) ); }; - auto node_veer = [](auto&& node_veer, auto min_state) -> netxs::sptr + auto node_veer = [](auto&& node_veer, auto min_state, auto grip_bindings_ptr) -> netxs::sptr { return ui::veer::ctor() ->plugin() @@ -566,7 +599,7 @@ namespace netxs::app::tile } } }; - boss.LISTEN(tier::release, app::tile::events::ui::split::any, gear) + boss.LISTEN(tier::release, app::tile::events::ui::split::any, gear, -, (grip_bindings_ptr)) { if (auto deed = boss.bell::protos(tier::release)) { @@ -576,9 +609,9 @@ namespace netxs::app::tile if (depth > inheritance_limit) return; auto heading = deed == app::tile::events::ui::split::vt.id; - auto newnode = build_node(heading ? 'v':'h', 1, 1, heading ? 1 : 2); - auto empty_1 = node_veer(node_veer, ui::fork::min_ratio); - auto empty_2 = node_veer(node_veer, ui::fork::max_ratio); + auto newnode = build_node(heading ? 'v':'h', 1, 1, heading ? 1 : 2, grip_bindings_ptr); + auto empty_1 = node_veer(node_veer, ui::fork::min_ratio, grip_bindings_ptr); + auto empty_2 = node_veer(node_veer, ui::fork::max_ratio, grip_bindings_ptr); auto gear_id_list = pro::focus::cut(boss.back()); auto curitem = boss.pop_back(); if (boss.empty()) @@ -664,9 +697,9 @@ namespace netxs::app::tile }) ->branch(empty_slot()); }; - auto parse_data = [](auto&& parse_data, view& utf8, auto min_ratio) -> netxs::sptr + auto parse_data = [](auto&& parse_data, view& utf8, auto min_ratio, auto grip_bindings_ptr) -> netxs::sptr { - auto slot = node_veer(node_veer, min_ratio); + auto slot = node_veer(node_veer, min_ratio, grip_bindings_ptr); utf::trim_front(utf8, ", "); if (utf8.empty()) return slot; auto tag = utf8.front(); @@ -701,9 +734,9 @@ namespace netxs::app::tile } if (utf8.empty() || utf8.front() != '(') return slot; utf8.remove_prefix(1); - auto node = build_node(tag, s1, s2, w); - auto slot1 = node->attach(slot::_1, parse_data(parse_data, utf8, ui::fork::min_ratio)); - auto slot2 = node->attach(slot::_2, parse_data(parse_data, utf8, ui::fork::max_ratio)); + auto node = build_node(tag, s1, s2, w, grip_bindings_ptr); + auto slot1 = node->attach(slot::_1, parse_data(parse_data, utf8, ui::fork::min_ratio, grip_bindings_ptr)); + auto slot2 = node->attach(slot::_2, parse_data(parse_data, utf8, ui::fork::max_ratio, grip_bindings_ptr)); slot->attach(node); utf::trim_front(utf8, ") "); } @@ -1078,16 +1111,20 @@ namespace netxs::app::tile { auto deed = boss.bell::protos(tier::preview); auto root_veer_ptr = boss.base::subset[1]; - foreach(root_veer_ptr, gear.id, [&](auto& item_ptr, si32 /*item_type*/, auto) + foreach(root_veer_ptr, gear.id, [&](auto& item_ptr, si32 /*item_type*/, auto node_veer_ptr) { - boss.bell::enqueue(boss.This(), [&, deed, gear_id = gear.id, item_wptr = ptr::shadow(item_ptr)](auto& /*boss*/) // Enqueue to keep the focus tree intact while processing key events. + auto room = node_veer_ptr->base::size() / 3; + if (room.x && room.y) // Suppress split if there is no space. { - if (auto gear_ptr = boss.bell::template getref(gear_id)) - if (auto item_ptr = item_wptr.lock()) + boss.bell::enqueue(boss.This(), [&, deed, gear_id = gear.id, item_wptr = ptr::shadow(item_ptr)](auto& /*boss*/) // Enqueue to keep the focus tree intact while processing key events. { - item_ptr->base::raw_riseup(tier::release, deed, *gear_ptr); - } - }); + if (auto gear_ptr = boss.bell::template getref(gear_id)) + if (auto item_ptr = item_wptr.lock()) + { + item_ptr->base::raw_riseup(tier::release, deed, *gear_ptr); + } + }); + } gear.set_handled(); }); }; @@ -1152,6 +1189,7 @@ namespace netxs::app::tile { tile::action::TileClosePane , [](auto& boss, auto& /*item*/){ on_left_click(boss, app::tile::events::ui::close ); }}, }; config.cd("/config/tile", "/config/defapp"); + auto grip_bindings_ptr = ptr::shared(pro::keybd::load(config, "tile/grips")); auto [menu_block, cover, menu_data] = menu::load(config, proc_map); object->attach(slot::_1, menu_block) ->invoke([](auto& boss) @@ -1182,7 +1220,7 @@ namespace netxs::app::tile if (err) log("%%Failed to change current directory to '%cwd%', error code: %error%", prompt::tile, appcfg.cwd, err.value()); else log("%%Change current directory to '%cwd%'", prompt::tile, appcfg.cwd); } - object->attach(slot::_2, parse_data(parse_data, param, ui::fork::min_ratio)) + object->attach(slot::_2, parse_data(parse_data, param, ui::fork::min_ratio, grip_bindings_ptr)) ->invoke([&](auto& boss) { boss.LISTEN(tier::release, e2::form::proceed::attach, fullscreen_item) diff --git a/src/netxs/desktopio/application.hpp b/src/netxs/desktopio/application.hpp index 0ab9822f12..61beb59b13 100644 --- a/src/netxs/desktopio/application.hpp +++ b/src/netxs/desktopio/application.hpp @@ -24,7 +24,7 @@ namespace netxs::app namespace netxs::app::shared { - static const auto version = "v0.9.99.56"; + static const auto version = "v0.9.99.57"; static const auto repository = "https://github.com/directvt/vtm"; static const auto usr_config = "~/.config/vtm/settings.xml"s; static const auto sys_config = "/etc/vtm/settings.xml"s; diff --git a/src/netxs/desktopio/controls.hpp b/src/netxs/desktopio/controls.hpp index e360be682d..d62c07db87 100644 --- a/src/netxs/desktopio/controls.hpp +++ b/src/netxs/desktopio/controls.hpp @@ -2048,7 +2048,6 @@ namespace netxs::ui std::unordered_map>>, bool>, qiew::hash, qiew::equal> handlers; std::unordered_map api_map; - subs tokens; bool interrupt_key_proc; std::unordered_map last_key; si64 instance_id; @@ -2129,48 +2128,40 @@ namespace netxs::ui interrupt_key_proc{ faux }, instance_id{ datetime::now().time_since_epoch().count() } { - - boss.LISTEN(tier::anycast, e2::form::upon::started, root, memo) + boss.LISTEN(tier::general, hids::events::die, gear, memo) + { + last_key.erase(gear.id); + }; + boss.LISTEN(tier::release, hids::events::keybd::key::any, gear, memo) { - tokens.clear(); - if (auto focusable_parent_ptr = boss.base::riseup(tier::request, e2::config::plugins::focus::owner)) + gear.shared_event = gear.touched && gear.touched != instance_id; + auto& timecod = last_key[gear.id]; + if (gear.timecod > timecod) { - focusable_parent_ptr->LISTEN(tier::release, hids::events::die, gear, tokens) - { - last_key.erase(gear.id); - }; - focusable_parent_ptr->LISTEN(tier::release, hids::events::keybd::key::any, gear, tokens) - { - gear.shared_event = gear.touched && gear.touched != instance_id; - auto& timecod = last_key[gear.id]; - if (gear.timecod > timecod) - { - timecod = gear.timecod; - if (gear.payload == input::keybd::type::keypress) - { - interrupt_key_proc = faux; - if (!gear.handled) _dispatch(gear, faux, input::key::kmap::any_key); - if (!gear.handled) _dispatch(gear, faux, gear.vkchord); - if (!gear.handled) _dispatch(gear, faux, gear.chchord); - if (!gear.handled) _dispatch(gear, faux, gear.scchord); - } - } - else - { - gear.set_handled(); - } - }; - focusable_parent_ptr->LISTEN(tier::preview, hids::events::keybd::key::any, gear, tokens) + timecod = gear.timecod; + if (gear.payload == input::keybd::type::keypress) { - gear.shared_event = gear.touched && gear.touched != instance_id; - if (gear.payload == input::keybd::type::keypress) - { - if (!gear.touched && !gear.handled) _dispatch(gear, true, gear.vkchord); - if (!gear.touched && !gear.handled) _dispatch(gear, true, gear.chchord); - if (!gear.touched && !gear.handled) _dispatch(gear, true, gear.scchord); - if (!gear.touched && !gear.handled) _dispatch(gear, true, input::key::kmap::any_key); - } - }; + interrupt_key_proc = faux; + if (!gear.handled) _dispatch(gear, faux, input::key::kmap::any_key); + if (!gear.handled) _dispatch(gear, faux, gear.vkchord); + if (!gear.handled) _dispatch(gear, faux, gear.chchord); + if (!gear.handled) _dispatch(gear, faux, gear.scchord); + } + } + else + { + gear.set_handled(); + } + }; + boss.LISTEN(tier::preview, hids::events::keybd::key::any, gear, memo) + { + gear.shared_event = gear.touched && gear.touched != instance_id; + if (gear.payload == input::keybd::type::keypress) + { + if (!gear.touched && !gear.handled) _dispatch(gear, true, gear.vkchord); + if (!gear.touched && !gear.handled) _dispatch(gear, true, gear.chchord); + if (!gear.touched && !gear.handled) _dispatch(gear, true, gear.scchord); + if (!gear.touched && !gear.handled) _dispatch(gear, true, input::key::kmap::any_key); } }; proc("Noop", [&](hids& gear, txts&){ gear.set_handled(); interrupt_key_proc = true; }); @@ -2188,7 +2179,8 @@ namespace netxs::ui } void proc(qiew name, func proc) { - api_map[name] = ptr::shared(std::move(proc)); + //api_map[name] = ptr::shared(std::move(proc)); + api_map[name] = ptr::shared(proc); } auto bind(qiew chord_str, auto&& proc_names, bool preview = faux) { @@ -2229,7 +2221,7 @@ namespace netxs::ui { for (auto& proc_name : proc_names) { - auto args_ptr = ptr::shared(std::move(proc_name.args)); + auto args_ptr = ptr::shared(proc_name.args); set(proc_name.action, args_ptr); } } @@ -2920,10 +2912,14 @@ namespace netxs::ui { return rotation == axis::X ? p : twod{ p.y, p.x }; } + void _set_grip_width(si32 grip_width) + { + griparea.size = xpose({ std::max(0, grip_width), 0 }); + } void _config(axis orientation, si32 grip_width, si32 s1 = 1, si32 s2 = 1) { rotation = orientation; - griparea.size = xpose({ std::max(0, grip_width), 0 }); + _set_grip_width(grip_width); _config_ratio(s1, s2); } void _config_ratio(si32 s1, si32 s2) @@ -3046,6 +3042,12 @@ namespace netxs::ui fraction = new_ratio; } // fork: . + auto set_grip_width(si32 new_grip_width) + { + _set_grip_width(new_grip_width); + base::reflow(); + } + // fork: . void config(si32 s1, si32 s2 = 1) { _config_ratio(s1, s2); @@ -3058,6 +3060,11 @@ namespace netxs::ui return This(); } // fork: . + auto get_config() + { + return std::tuple{ rotation, griparea, fraction }; + } + // fork: . void rotate() { auto width = xpose(griparea.size).x; @@ -3078,7 +3085,7 @@ namespace netxs::ui { if (splitter) { - auto delta = griparea.size * xpose({ step, 0 }); + auto delta = std::max(dot_11, griparea.size) * xpose({ step, 0 }); splitter->bell::signal(tier::preview, e2::form::upon::changed, delta); } } diff --git a/src/netxs/desktopio/xml.hpp b/src/netxs/desktopio/xml.hpp index f8fb16b439..0c85be7d2d 100644 --- a/src/netxs/desktopio/xml.hpp +++ b/src/netxs/desktopio/xml.hpp @@ -10,6 +10,7 @@ namespace netxs::xml template auto take(qiew utf8) -> std::optional { + utf::trim_front(utf8); if (utf8.starts_with("0x")) { utf8.remove_prefix(2); @@ -21,6 +22,7 @@ namespace netxs::xml template<> auto take(qiew utf8) -> std::optional { + utf::trim_front(utf8); return utf8 ? utf::to_int(utf8) : std::nullopt; } @@ -32,6 +34,7 @@ namespace netxs::xml template<> auto take(qiew utf8) -> std::optional { + utf::trim_front(utf8); auto value = utf::to_lower(utf8.str()); if (value.starts_with("undef")) return std::nullopt; // Use default. if (value.empty() || value == "1" diff --git a/src/vtm.xml b/src/vtm.xml index 8243f78e21..5afb1136d3 100644 --- a/src/vtm.xml +++ b/src/vtm.xml @@ -454,23 +454,25 @@ R"==( - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + )=="