Compare commits

...

14 Commits

Author SHA1 Message Date
github-actions[bot]
9599ac28ca chore: bump Cargo.toml to 0.6.2 2025-12-12 17:02:43 +00:00
github-actions[bot]
e71a699ed8 bump: version 0.6.1 → 0.6.2 [skip ci] 2025-12-12 17:02:30 +00:00
ff4eb8ca98 ci: Specify commitizen version [skip ci] 2025-12-12 10:00:11 -07:00
b69973b9af ci: Fix the commitizen version issue [skip ci] 2025-12-12 09:54:15 -07:00
3e133fa147 refactor: Replaced all modulo usages of tick_until_poll with is_multiple_of 2025-12-12 09:20:05 -07:00
ae506789ab fix: Fixed breaking Sonarr Episode file API calls after recent Sonarr API update 2025-12-12 09:17:36 -07:00
c4e8d64710 ci: Fixed changelog generation for releases 2025-09-14 18:46:28 -06:00
ca4319001c docs: Updated the Homebrew tap description [skip-ci] 2025-09-12 11:25:20 -06:00
ebc58b831d ci: Modified the CI/CD pipeline to bump the procedural macros as well 2025-09-02 17:27:12 -06:00
github-actions[bot]
b1572c903c chore: bump Cargo.toml to 0.6.1 2025-09-02 22:58:28 +00:00
github-actions[bot]
5b73924e2a bump: version 0.6.0 → 0.6.1 [skip ci] 2025-09-02 22:58:25 +00:00
97ce258a8d fix: Fixed UI bugs introduced as part of the hotkey refactor 2025-09-02 15:49:36 -06:00
fedec79a95 refactor: Updated crate to publish properly with the procedural macros 2025-08-29 17:31:14 -06:00
8e74709b9c chore(release): Add metadata to enum_display_style_derive 2025-08-29 17:22:15 -06:00
37 changed files with 901 additions and 592 deletions
+91 -13
View File
@@ -8,9 +8,9 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
bump_type: bump_type:
description: "Specify the type of version bump" description: 'Specify the type of version bump'
required: true required: true
default: "patch" default: 'patch'
type: choice type: choice
options: options:
- patch - patch
@@ -46,12 +46,12 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: '3.10'
- name: Install Commitizen - name: Install Commitizen
run: | run: |
python -m pip install --upgrade pip python3 -m pip install --upgrade pip
pip install commitizen pip3 install commitizen==4.8.3
npm install -g conventional-changelog-cli npm install -g conventional-changelog-cli
- name: Configure Git user - name: Configure Git user
@@ -126,12 +126,90 @@ jobs:
echo "No changes to commit (already at $VERSION)" echo "No changes to commit (already at $VERSION)"
fi fi
- name: Bump validate_theme_derive/Cargo.toml version
shell: bash
working-directory: ${{ github.workspace }}/proc_macros/validate_theme_derive
env:
VERSION: ${{ env.version }}
run: |
set -euo pipefail
: "${VERSION:?env.version is empty}"
# Ignore Act's local artifact dir noise
echo artifacts/ >> .git/info/exclude || true
# Edit the version line right after name="managarr"
sed -E -i '
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
n
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
}
' Cargo.toml
cargo update || true
# Git config that helps in containers (Act)
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
# Debug: show what changed
git status --porcelain
git diff --name-only -- Cargo.toml || true
# Only commit if one of these files actually changed
if ! git diff --quiet -- Cargo.toml; then
# Stage only modifications of already tracked files (won't pick up artifacts/)
git add -u -- Cargo.toml
git commit -m "chore: bump validate_theme_derive Cargo.toml to $VERSION"
else
echo "No changes to commit (already at $VERSION)"
fi
- name: Bump enum_display_style_derive/Cargo.toml version
shell: bash
working-directory: ${{ github.workspace }}/proc_macros/enum_display_style_derive
env:
VERSION: ${{ env.version }}
run: |
set -euo pipefail
: "${VERSION:?env.version is empty}"
# Ignore Act's local artifact dir noise
echo artifacts/ >> .git/info/exclude || true
# Edit the version line right after name="managarr"
sed -E -i '
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
n
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
}
' Cargo.toml
cargo update || true
# Git config that helps in containers (Act)
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
# Debug: show what changed
git status --porcelain
git diff --name-only -- Cargo.toml || true
# Only commit if one of these files actually changed
if ! git diff --quiet -- Cargo.toml; then
# Stage only modifications of already tracked files (won't pick up artifacts/)
git add -u -- Cargo.toml
git commit -m "chore: bump enum_display_style_derive Cargo.toml to $VERSION"
else
echo "No changes to commit (already at $VERSION)"
fi
- name: Generate changelog for the version bump - name: Generate changelog for the version bump
id: changelog id: changelog
run: | run: |
changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }}) conventional-changelog -p angular -i CHANGELOG.md --from ${{ env.prev_version }} --to v${{ env.version }} > artifacts/changelog.md
echo "$changelog" > artifacts/changelog.md
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
- name: Push changes - name: Push changes
if: env.ACT != 'true' if: env.ACT != 'true'
@@ -153,6 +231,8 @@ jobs:
path: | path: |
Cargo.toml Cargo.toml
Cargo.lock Cargo.lock
proc_macros/validate_theme_derive/Cargo.toml
proc_macros/enum_display_style_derive/Cargo.toml
build-release-artifacts: build-release-artifacts:
name: build-release name: build-release
@@ -333,13 +413,11 @@ jobs:
run: | run: |
release_version="$(cat ./artifacts/release-version)" release_version="$(cat ./artifacts/release-version)"
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
changelog_body="$(cat ./artifacts/changelog.md)"
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
- name: Validate release environment variables - name: Validate release environment variables
run: | run: |
echo "Release version: ${{ env.RELEASE_VERSION }}" echo "Release version: ${{ env.RELEASE_VERSION }}"
echo "Changelog body: ${{ env.changelog_body }}" echo "Changelog body: $(cat artifacts/changelog.md)"
- name: Create a GitHub Release - name: Create a GitHub Release
if: env.ACT != 'true' if: env.ACT != 'true'
@@ -373,8 +451,8 @@ jobs:
artifacts/managarr-armv7-musl.tar.gz artifacts/managarr-armv7-musl.tar.gz
artifacts/managarr-armv7-musl.sha256 artifacts/managarr-armv7-musl.sha256
tag_name: v${{ env.RELEASE_VERSION }} tag_name: v${{ env.RELEASE_VERSION }}
name: "v${{ env.RELEASE_VERSION }}" name: 'v${{ env.RELEASE_VERSION }}'
body: ${{ env.changelog_body }} body_path: artifacts/changelog.md
draft: false draft: false
prerelease: false prerelease: false
+20
View File
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.6.2 (2025-12-12)
### Fix
- Fixed breaking Sonarr Episode file API calls after recent Sonarr API update
### Refactor
- Replaced all modulo usages of tick_until_poll with is_multiple_of
## v0.6.1 (2025-09-02)
### Fix
- Fixed UI bugs introduced as part of the hotkey refactor
### Refactor
- Updated crate to publish properly with the procedural macros
## v0.6.0 (2025-08-29) ## v0.6.0 (2025-08-29)
### Feat ### Feat
Generated
+673 -484
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "managarr" name = "managarr"
version = "0.6.0" version = "0.6.2"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI and CLI to manage your Servarrs" description = "A TUI and CLI to manage your Servarrs"
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"] keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
@@ -66,8 +66,8 @@ deunicode = "1.6.0"
paste = "1.0.15" paste = "1.0.15"
openssl = { version = "0.10.70", features = ["vendored"] } openssl = { version = "0.10.70", features = ["vendored"] }
veil = "0.2.0" veil = "0.2.0"
validate_theme_derive = { path = "proc_macros/validate_theme_derive" } validate_theme_derive = "0.1.0"
enum_display_style_derive = { path = "proc_macros/enum_display_style_derive" } enum_display_style_derive = "0.1.0"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.16" assert_cmd = "2.0.16"
+1 -1
View File
@@ -1,7 +1,7 @@
# Documentation: https://docs.brew.sh/Formula-Cookbook # Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula # https://rubydoc.brew.sh/Formula
class Managarr < Formula class Managarr < Formula
desc "A fast and simple dashboard for Kubernetes written in Rust" desc "Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC)"
homepage "https://github.com/Dark-Alex-17/managarr" homepage "https://github.com/Dark-Alex-17/managarr"
if OS.mac? and Hardware::CPU.arm? if OS.mac? and Hardware::CPU.arm?
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz" url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz"
@@ -1,7 +1,13 @@
[package] [package]
name = "enum_display_style_derive" name = "enum_display_style_derive"
version = "0.1.0" version = "0.6.1"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A proc-macro to derive a `Display` and `FromStr` implementation for enums with a `style` attribute."
license = "MIT"
documentation = "https://github.com/Dark-Alex-17/managarr"
repository = "https://github.com/Dark-Alex-17/managarr"
homepage = "https://github.com/Dark-Alex-17/managarr"
[lib] [lib]
proc-macro = true proc-macro = true
+7 -1
View File
@@ -1,7 +1,13 @@
[package] [package]
name = "validate_theme_derive" name = "validate_theme_derive"
version = "0.1.0" version = "0.6.1"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A proc-macro to validate a theme."
license = "MIT"
documentation = "https://github.com/Dark-Alex-17/managarr"
repository = "https://github.com/Dark-Alex-17/managarr"
homepage = "https://github.com/Dark-Alex-17/managarr"
[lib] [lib]
proc-macro = true proc-macro = true
+4 -1
View File
@@ -160,7 +160,10 @@ impl App<'_> {
} }
pub async fn on_tick(&mut self) { pub async fn on_tick(&mut self) {
if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh { if self.tick_count.is_multiple_of(self.tick_until_poll)
|| self.is_routing
|| self.should_refresh
{
match self.get_current_route() { match self.get_current_route() {
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await, Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await, Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
+1 -1
View File
@@ -185,7 +185,7 @@ impl App<'_> {
} }
} }
if self.tick_count % self.tick_until_poll == 0 { if self.tick_count.is_multiple_of(self.tick_until_poll) {
self.refresh_radarr_metadata().await; self.refresh_radarr_metadata().await;
} }
} }
+1 -1
View File
@@ -215,7 +215,7 @@ impl App<'_> {
} }
} }
if self.tick_count % self.tick_until_poll == 0 { if self.tick_count.is_multiple_of(self.tick_until_poll) {
self.refresh_sonarr_metadata().await; self.refresh_sonarr_metadata().await;
} }
} }
@@ -228,7 +228,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(), path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(),
size: 3543348019, size: 3543348019,
quality: quality_wrapper(), quality: quality_wrapper(),
languages: vec![language()], languages: vec![Some(language())],
date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
media_info: Some(media_info()), media_info: Some(media_info()),
} }
+1 -1
View File
@@ -215,7 +215,7 @@ pub struct EpisodeFile {
pub path: String, pub path: String,
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub size: i64, pub size: i64,
pub languages: Vec<Language>, pub languages: Vec<Option<Language>>,
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub date_added: DateTime<Utc>, pub date_added: DateTime<Utc>,
pub media_info: Option<MediaInfo>, pub media_info: Option<MediaInfo>,
@@ -297,7 +297,13 @@ impl Network<'_, '_> {
Date Added: {}", Date Added: {}",
file.relative_path, file.relative_path,
file.path, file.path,
file.languages.first().unwrap_or(&Language::default()).name, file
.languages
.first()
.unwrap_or(&Some(Language::default()))
.as_ref()
.unwrap_or(&Language::default())
.name,
file.date_added, file.date_added,
); );
@@ -180,7 +180,7 @@ pub(in crate::network::sonarr_network) mod test_utils {
path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(), path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(),
size: 3543348019, size: 3543348019,
quality: quality_wrapper(), quality: quality_wrapper(),
languages: vec![language()], languages: vec![Some(language())],
date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
media_info: Some(media_info()), media_info: Some(media_info()),
} }
+1 -1
View File
@@ -121,7 +121,7 @@ fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
app.error.scroll_left_or_reset( app.error.scroll_left_or_reset(
area.width as usize, area.width as usize,
true, true,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let paragraph = Paragraph::new(Text::from(app.error.to_string().failure())) let paragraph = Paragraph::new(Text::from(app.error.to_string().failure()))
+1 -1
View File
@@ -97,7 +97,7 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 20), get_width_from_percentage(area, 20),
current_selection == *blocklist_item, current_selection == *blocklist_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let languages_string = languages let languages_string = languages
@@ -1,4 +1,4 @@
use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect}; use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::style::Stylize; use ratatui::style::Stylize;
use ratatui::text::{Line, Text}; use ratatui::text::{Line, Text};
use ratatui::widgets::{Cell, Paragraph, Row, Wrap}; use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
@@ -90,7 +90,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(table_area, 20), get_width_from_percentage(table_area, 20),
current_selection == *movie, current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let (hours, minutes) = convert_runtime(movie.runtime); let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie let imdb_rating = movie
@@ -184,7 +184,6 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
) )
.block(layout_block_top_border_with_title(title_style("Movies"))) .block(layout_block_top_border_with_title(title_style("Movies")))
.loading(app.is_loading) .loading(app.is_loading)
.footer_alignment(Alignment::Center)
.headers([ .headers([
"", "",
"Title", "Title",
+1 -1
View File
@@ -71,7 +71,7 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
collection.title.scroll_left_or_reset( collection.title.scroll_left_or_reset(
get_width_from_percentage(area, 25), get_width_from_percentage(area, 25),
*collection == current_selection, *collection == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let monitored = if collection.monitored { "🏷" } else { "" }; let monitored = if collection.monitored { "🏷" } else { "" };
let search_on_add = if collection.search_on_add { let search_on_add = if collection.search_on_add {
+1 -1
View File
@@ -88,7 +88,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
output_path.as_ref().unwrap().scroll_left_or_reset( output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18), get_width_from_percentage(area, 18),
current_selection == *download_record, current_selection == *download_record,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
} }
@@ -7,7 +7,7 @@ use crate::ui::utils::{get_width_from_percentage, title_block};
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::popup::Size; use crate::ui::widgets::popup::Size;
use crate::ui::{draw_popup, DrawUi}; use crate::ui::{draw_popup, DrawUi};
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Constraint, Rect};
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -46,7 +46,7 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
result.validation_failures.scroll_left_or_reset( result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86), get_width_from_percentage(area, 86),
*result == current_selection, *result == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let pass_fail = if result.is_valid { "" } else { "" }; let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![ let row = Row::new(vec![
@@ -67,7 +67,6 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
test_results_row_mapping, test_results_row_mapping,
) )
.loading(is_loading) .loading(is_loading)
.footer_alignment(Alignment::Center)
.margin(1) .margin(1)
.headers(["Indexer", "Pass/Fail", "Failure Messages"]) .headers(["Indexer", "Pass/Fail", "Failure Messages"])
.constraints([ .constraints([
+1 -1
View File
@@ -134,7 +134,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*movie == current_selection, *movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
+1 -1
View File
@@ -90,7 +90,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie.title.scroll_left_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*movie == current_selection, *movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let monitored = if movie.monitored { "🏷" } else { "" }; let monitored = if movie.monitored { "🏷" } else { "" };
let studio = movie.studio.clone().unwrap_or_default(); let studio = movie.studio.clone().unwrap_or_default();
+2 -2
View File
@@ -241,7 +241,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
movie_history_item.source_title.scroll_left_or_reset( movie_history_item.source_title.scroll_left_or_reset(
get_width_from_percentage(area, 34), get_width_from_percentage(area, 34),
current_selection == *movie_history_item, current_selection == *movie_history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -393,7 +393,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30), get_width_from_percentage(area, 30),
current_selection == *release current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(), && current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let size = convert_to_gb(*size); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
+11 -6
View File
@@ -1,4 +1,4 @@
use ratatui::layout::{Alignment, Rect}; use ratatui::layout::Rect;
use ratatui::text::{Span, Text}; use ratatui::text::{Span, Text};
use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -60,7 +60,7 @@ fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
if app.data.radarr_data.log_details.items.is_empty() { if app.data.radarr_data.log_details.items.is_empty() {
let loading = LoadingBlock::new(app.is_loading, borderless_block()); let loading = LoadingBlock::new(app.is_loading, borderless_block());
let popup = Popup::new(loading).size(Size::Large).block(block); let popup = Popup::new(loading).size(Size::Large).block(block).margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
return; return;
@@ -73,7 +73,10 @@ fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
}) })
.block(borderless_block()); .block(borderless_block());
let popup = Popup::new(logs_list).size(Size::Large).block(block); let popup = Popup::new(logs_list)
.size(Size::Large)
.block(block)
.margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
} }
@@ -94,7 +97,6 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let tasks_table = ManagarrTable::new(Some(&mut app.data.radarr_data.tasks), tasks_row_mapping) let tasks_table = ManagarrTable::new(Some(&mut app.data.radarr_data.tasks), tasks_row_mapping)
.loading(app.is_loading) .loading(app.is_loading)
.margin(1) .margin(1)
.footer_alignment(Alignment::Center)
.headers(TASK_TABLE_HEADERS) .headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS); .constraints(TASK_TABLE_CONSTRAINTS);
@@ -129,12 +131,15 @@ fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
let updates_paragraph = Paragraph::new(Text::from(updates)) let updates_paragraph = Paragraph::new(Text::from(updates))
.block(borderless_block()) .block(borderless_block())
.scroll((app.data.radarr_data.updates.offset, 0)); .scroll((app.data.radarr_data.updates.offset, 0));
let popup = Popup::new(updates_paragraph).size(Size::Large).block(block); let popup = Popup::new(updates_paragraph)
.size(Size::Large)
.block(block)
.margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
} else { } else {
let loading = LoadingBlock::new(app.is_loading, borderless_block()); let loading = LoadingBlock::new(app.is_loading, borderless_block());
let popup = Popup::new(loading).size(Size::Large).block(block); let popup = Popup::new(loading).size(Size::Large).block(block).margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
} }
+1 -1
View File
@@ -88,7 +88,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
output_path.as_ref().unwrap().scroll_left_or_reset( output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18), get_width_from_percentage(area, 18),
current_selection == *download_record, current_selection == *download_record,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
} }
+1 -1
View File
@@ -68,7 +68,7 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -7,7 +7,7 @@ use crate::ui::utils::{get_width_from_percentage, title_block};
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::popup::Size; use crate::ui::widgets::popup::Size;
use crate::ui::{draw_popup, DrawUi}; use crate::ui::{draw_popup, DrawUi};
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Constraint, Rect};
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -44,7 +44,7 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
result.validation_failures.scroll_left_or_reset( result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86), get_width_from_percentage(area, 86),
*result == current_selection, *result == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let pass_fail = if result.is_valid { "" } else { "" }; let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![ let row = Row::new(vec![
@@ -65,7 +65,6 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
test_results_row_mapping, test_results_row_mapping,
) )
.loading(is_loading) .loading(is_loading)
.footer_alignment(Alignment::Center)
.margin(1) .margin(1)
.headers(["Indexer", "Pass/Fail", "Failure Messages"]) .headers(["Indexer", "Pass/Fail", "Failure Messages"])
.constraints([ .constraints([
+1 -1
View File
@@ -120,7 +120,7 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
series.title.scroll_left_or_reset( series.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*series == current_selection, *series == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -271,7 +271,7 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -415,7 +415,7 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30), get_width_from_percentage(area, 30),
current_selection == *release current_selection == *release
&& active_sonarr_block != ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt, && active_sonarr_block != ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let size = convert_to_gb(*size); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
+1 -1
View File
@@ -95,7 +95,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
series.title.scroll_left_or_reset( series.title.scroll_left_or_reset(
get_width_from_percentage(area, 23), get_width_from_percentage(area, 23),
*series == current_selection, *series == current_selection,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let monitored = if series.monitored { "🏷" } else { "" }; let monitored = if series.monitored { "🏷" } else { "" };
let certification = series.certification.clone().unwrap_or_default(); let certification = series.certification.clone().unwrap_or_default();
@@ -267,7 +267,7 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
@@ -372,7 +372,7 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30), get_width_from_percentage(area, 30),
current_selection == *release current_selection == *release
&& active_sonarr_block != ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt, && active_sonarr_block != ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
let size = convert_to_gb(*size); let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
@@ -315,7 +315,7 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset( source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40), get_width_from_percentage(area, 40),
current_selection == *history_item, current_selection == *history_item,
app.tick_count % app.ticks_until_scroll == 0, app.tick_count.is_multiple_of(app.ticks_until_scroll),
); );
Row::new(vec![ Row::new(vec![
+11 -6
View File
@@ -1,4 +1,4 @@
use ratatui::layout::{Alignment, Rect}; use ratatui::layout::Rect;
use ratatui::text::{Span, Text}; use ratatui::text::{Span, Text};
use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -60,7 +60,7 @@ fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
if app.data.sonarr_data.log_details.items.is_empty() { if app.data.sonarr_data.log_details.items.is_empty() {
let loading = LoadingBlock::new(app.is_loading, borderless_block()); let loading = LoadingBlock::new(app.is_loading, borderless_block());
let popup = Popup::new(loading).size(Size::Large).block(block); let popup = Popup::new(loading).size(Size::Large).block(block).margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
return; return;
@@ -73,7 +73,10 @@ fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
}) })
.block(borderless_block()); .block(borderless_block());
let popup = Popup::new(logs_list).size(Size::Large).block(block); let popup = Popup::new(logs_list)
.size(Size::Large)
.block(block)
.margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
} }
@@ -93,7 +96,6 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let tasks_table = ManagarrTable::new(Some(&mut app.data.sonarr_data.tasks), tasks_row_mapping) let tasks_table = ManagarrTable::new(Some(&mut app.data.sonarr_data.tasks), tasks_row_mapping)
.loading(app.is_loading) .loading(app.is_loading)
.margin(1) .margin(1)
.footer_alignment(Alignment::Center)
.headers(TASK_TABLE_HEADERS) .headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS); .constraints(TASK_TABLE_CONSTRAINTS);
@@ -128,12 +130,15 @@ fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
let updates_paragraph = Paragraph::new(Text::from(updates)) let updates_paragraph = Paragraph::new(Text::from(updates))
.block(borderless_block()) .block(borderless_block())
.scroll((app.data.sonarr_data.updates.offset, 0)); .scroll((app.data.sonarr_data.updates.offset, 0));
let popup = Popup::new(updates_paragraph).size(Size::Large).block(block); let popup = Popup::new(updates_paragraph)
.size(Size::Large)
.block(block)
.margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
} else { } else {
let loading = LoadingBlock::new(app.is_loading, borderless_block()); let loading = LoadingBlock::new(app.is_loading, borderless_block());
let popup = Popup::new(loading).size(Size::Large).block(block); let popup = Popup::new(loading).size(Size::Large).block(block).margin(1);
f.render_widget(popup, f.area()); f.render_widget(popup, f.area());
} }
+9 -31
View File
@@ -3,18 +3,16 @@ use super::message::Message;
use super::popup::Size; use super::popup::Size;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{borderless_block, centered_rect, title_block_centered};
borderless_block, centered_rect, layout_block_top_border, title_block_centered,
};
use crate::ui::widgets::loading_block::LoadingBlock; use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::popup::Popup; use crate::ui::widgets::popup::Popup;
use crate::ui::widgets::selectable_list::SelectableList; use crate::ui::widgets::selectable_list::SelectableList;
use crate::ui::HIGHLIGHT_SYMBOL; use crate::ui::HIGHLIGHT_SYMBOL;
use derive_setters::Setters; use derive_setters::Setters;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Layout, Position, Rect}; use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::prelude::{Style, Stylize, Text}; use ratatui::prelude::{Style, Stylize, Text};
use ratatui::widgets::{Block, ListItem, Paragraph, Row, StatefulWidget, Table, Widget, WidgetRef}; use ratatui::widgets::{Block, ListItem, Row, StatefulWidget, Table, Widget, WidgetRef};
use ratatui::Frame; use ratatui::Frame;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@@ -36,8 +34,6 @@ where
#[setters(skip)] #[setters(skip)]
constraints: Vec<Constraint>, constraints: Vec<Constraint>,
row_mapper: F, row_mapper: F,
footer: Option<String>,
footer_alignment: Alignment,
block: Block<'a>, block: Block<'a>,
margin: u16, margin: u16,
#[setters(rename = "loading")] #[setters(rename = "loading")]
@@ -68,8 +64,6 @@ where
table_headers: Vec::new(), table_headers: Vec::new(),
constraints: Vec::new(), constraints: Vec::new(),
row_mapper, row_mapper,
footer: None,
footer_alignment: Alignment::Left,
block: borderless_block(), block: borderless_block(),
margin: 0, margin: 0,
is_loading: false, is_loading: false,
@@ -119,20 +113,12 @@ where
fn render_table(self, area: Rect, buf: &mut Buffer) { fn render_table(self, area: Rect, buf: &mut Buffer) {
let table_headers = self.parse_headers(); let table_headers = self.parse_headers();
let table_area = if let Some(ref footer) = self.footer { let table_area = {
let [content_area, footer_area] = let [content_area, _] = Layout::vertical([Constraint::Fill(1), Constraint::Fill(0)])
Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]) .margin(self.margin)
.margin(self.margin) .areas(area);
.areas(area);
Paragraph::new(Text::from(format!(" {footer}").help()))
.block(layout_block_top_border())
.alignment(self.footer_alignment)
.render(footer_area, buf);
content_area content_area
} else {
area
}; };
let loading_block = LoadingBlock::new(self.is_loading, self.block.clone()); let loading_block = LoadingBlock::new(self.is_loading, self.block.clone());
@@ -230,19 +216,11 @@ where
pub fn show_cursor(&self, f: &mut Frame<'_>, area: Rect) { pub fn show_cursor(&self, f: &mut Frame<'_>, area: Rect) {
let mut draw_cursor = |length: usize, offset: usize| { let mut draw_cursor = |length: usize, offset: usize| {
let table_area = if self.footer.is_some() {
let [content_area, _] = Layout::vertical([Constraint::Fill(0), Constraint::Length(2)])
.margin(self.margin)
.areas(area);
content_area
} else {
area
};
let popup_area = Rect { let popup_area = Rect {
height: 7, height: 7,
..centered_rect(30, 20, table_area) ..centered_rect(30, 20, area)
}; };
let [text_box_area, _] = Layout::vertical([Constraint::Length(3), Constraint::Length(1)]) let [text_box_area, _] = Layout::vertical([Constraint::Fill(1), Constraint::Fill(0)])
.margin(1) .margin(1)
.areas(popup_area); .areas(popup_area);
f.set_cursor_position(Position { f.set_cursor_position(Position {
+1 -11
View File
@@ -6,7 +6,7 @@ mod tests {
use crate::ui::utils::borderless_block; use crate::ui::utils::borderless_block;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use ratatui::layout::{Alignment, Constraint}; use ratatui::layout::Constraint;
use ratatui::text::Text; use ratatui::text::Text;
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
@@ -25,8 +25,6 @@ mod tests {
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
assert_eq!(managarr_table.table_headers, Vec::<String>::new()); assert_eq!(managarr_table.table_headers, Vec::<String>::new());
assert_eq!(managarr_table.constraints, Vec::new()); assert_eq!(managarr_table.constraints, Vec::new());
assert_eq!(managarr_table.footer, None);
assert_eq!(managarr_table.footer_alignment, Alignment::Left);
assert_eq!(managarr_table.block, borderless_block()); assert_eq!(managarr_table.block, borderless_block());
assert_eq!(managarr_table.margin, 0); assert_eq!(managarr_table.margin, 0);
assert!(!managarr_table.is_loading); assert!(!managarr_table.is_loading);
@@ -61,8 +59,6 @@ mod tests {
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
assert_eq!(managarr_table.table_headers, Vec::<String>::new()); assert_eq!(managarr_table.table_headers, Vec::<String>::new());
assert_eq!(managarr_table.constraints, Vec::new()); assert_eq!(managarr_table.constraints, Vec::new());
assert_eq!(managarr_table.footer, None);
assert_eq!(managarr_table.footer_alignment, Alignment::Left);
assert_eq!(managarr_table.block, borderless_block()); assert_eq!(managarr_table.block, borderless_block());
assert_eq!(managarr_table.margin, 0); assert_eq!(managarr_table.margin, 0);
assert!(!managarr_table.is_loading); assert!(!managarr_table.is_loading);
@@ -97,8 +93,6 @@ mod tests {
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
assert_eq!(managarr_table.table_headers, Vec::<String>::new()); assert_eq!(managarr_table.table_headers, Vec::<String>::new());
assert_eq!(managarr_table.constraints, Vec::new()); assert_eq!(managarr_table.constraints, Vec::new());
assert_eq!(managarr_table.footer, None);
assert_eq!(managarr_table.footer_alignment, Alignment::Left);
assert_eq!(managarr_table.block, borderless_block()); assert_eq!(managarr_table.block, borderless_block());
assert_eq!(managarr_table.margin, 0); assert_eq!(managarr_table.margin, 0);
assert!(!managarr_table.is_loading); assert!(!managarr_table.is_loading);
@@ -130,8 +124,6 @@ mod tests {
assert_eq!(managarr_table.content.unwrap().items, items); assert_eq!(managarr_table.content.unwrap().items, items);
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
assert_eq!(managarr_table.constraints, Vec::new()); assert_eq!(managarr_table.constraints, Vec::new());
assert_eq!(managarr_table.footer, None);
assert_eq!(managarr_table.footer_alignment, Alignment::Left);
assert_eq!(managarr_table.block, borderless_block()); assert_eq!(managarr_table.block, borderless_block());
assert_eq!(managarr_table.margin, 0); assert_eq!(managarr_table.margin, 0);
assert!(!managarr_table.is_loading); assert!(!managarr_table.is_loading);
@@ -163,8 +155,6 @@ mod tests {
assert_eq!(managarr_table.content.unwrap().items, items); assert_eq!(managarr_table.content.unwrap().items, items);
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
assert_eq!(managarr_table.table_headers, Vec::<String>::new()); assert_eq!(managarr_table.table_headers, Vec::<String>::new());
assert_eq!(managarr_table.footer, None);
assert_eq!(managarr_table.footer_alignment, Alignment::Left);
assert_eq!(managarr_table.block, borderless_block()); assert_eq!(managarr_table.block, borderless_block());
assert_eq!(managarr_table.margin, 0); assert_eq!(managarr_table.margin, 0);
assert!(!managarr_table.is_loading); assert!(!managarr_table.is_loading);
+13 -2
View File
@@ -1,6 +1,6 @@
use crate::ui::utils::{background_block, centered_rect}; use crate::ui::utils::{background_block, centered_rect};
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::Rect; use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::widgets::{Block, Clear, Widget}; use ratatui::widgets::{Block, Clear, Widget};
#[cfg(test)] #[cfg(test)]
@@ -51,6 +51,7 @@ impl Size {
pub struct Popup<'a, T: Widget> { pub struct Popup<'a, T: Widget> {
widget: T, widget: T,
margin: u16,
percent_x: u16, percent_x: u16,
percent_y: u16, percent_y: u16,
block: Option<Block<'a>>, block: Option<Block<'a>>,
@@ -62,6 +63,7 @@ impl<'a, T: Widget> Popup<'a, T> {
widget, widget,
percent_x: 0, percent_x: 0,
percent_y: 0, percent_y: 0,
margin: 0,
block: None, block: None,
} }
} }
@@ -84,6 +86,11 @@ impl<'a, T: Widget> Popup<'a, T> {
self self
} }
pub fn margin(mut self, margin: u16) -> Self {
self.margin = margin;
self
}
fn render_popup(self, area: Rect, buf: &mut Buffer) { fn render_popup(self, area: Rect, buf: &mut Buffer) {
let mut popup_area = centered_rect(self.percent_x, self.percent_y, area); let mut popup_area = centered_rect(self.percent_x, self.percent_y, area);
let height = if popup_area.height < 3 { let height = if popup_area.height < 3 {
@@ -102,7 +109,11 @@ impl<'a, T: Widget> Popup<'a, T> {
block.render(popup_area, buf); block.render(popup_area, buf);
} }
self.widget.render(popup_area, buf); let [content_area, _] = Layout::vertical([Constraint::Fill(1), Constraint::Fill(0)])
.margin(self.margin)
.areas(popup_area);
self.widget.render(content_area, buf);
} }
} }
+15
View File
@@ -32,6 +32,7 @@ mod tests {
assert_eq!(popup.percent_x, 0); assert_eq!(popup.percent_x, 0);
assert_eq!(popup.percent_y, 0); assert_eq!(popup.percent_y, 0);
assert_eq!(popup.block, None); assert_eq!(popup.block, None);
assert_eq!(popup.margin, 0);
} }
#[test] #[test]
@@ -42,6 +43,7 @@ mod tests {
assert_eq!(popup.percent_y, 40); assert_eq!(popup.percent_y, 40);
assert_eq!(popup.widget, Block::new()); assert_eq!(popup.widget, Block::new());
assert_eq!(popup.block, None); assert_eq!(popup.block, None);
assert_eq!(popup.margin, 0);
} }
#[test] #[test]
@@ -52,6 +54,7 @@ mod tests {
assert_eq!(popup.percent_y, 50); assert_eq!(popup.percent_y, 50);
assert_eq!(popup.widget, Block::new()); assert_eq!(popup.widget, Block::new());
assert_eq!(popup.block, None); assert_eq!(popup.block, None);
assert_eq!(popup.margin, 0);
} }
#[test] #[test]
@@ -62,5 +65,17 @@ mod tests {
assert_eq!(popup.widget, Block::new()); assert_eq!(popup.widget, Block::new());
assert_eq!(popup.percent_x, 0); assert_eq!(popup.percent_x, 0);
assert_eq!(popup.percent_y, 0); assert_eq!(popup.percent_y, 0);
assert_eq!(popup.margin, 0);
}
#[test]
fn test_popup_margin() {
let popup = Popup::new(Block::new()).margin(5);
assert_eq!(popup.margin, 5);
assert_eq!(popup.widget, Block::new());
assert_eq!(popup.percent_x, 0);
assert_eq!(popup.percent_y, 0);
assert_eq!(popup.block, None);
} }
} }