Compare commits

...

48 Commits

Author SHA1 Message Date
Piyush मिश्रः b66770620d Update README.md 2024-12-07 18:31:55 +05:30
Piyush मिश्रः 54466c1216
Merge pull request #7 from PiyushXCoder/revert-1-add/dependabot
Revert "add ci and dependabot"
2024-10-06 18:50:04 +05:30
Piyush मिश्र e0b686d66e
Revert "add ci and dependabot" 2024-10-06 18:48:47 +05:30
Piyush मिश्रः 80824ed3d8
Merge pull request #1 from theavege/add/dependabot
add ci and dependabot
2024-10-06 15:24:39 +05:30
Artem V. Ageev e1ed10805c small fix 2024-10-06 12:48:03 +03:00
Artem V. Ageev 8447939489 add ci and dependabot 2024-10-05 18:09:54 +03:00
Piyush मिश्रः aca3a0ec2d Readme shields 2022-08-19 15:03:13 +05:30
Piyush मिश्रः 9b6412b823 Version upgrade 0.6.3 2022-08-19 14:21:53 +05:30
Piyush मिश्रः 286af71348 Tags in Readme and license in files 2022-08-19 14:18:41 +05:30
Piyush मिश्रः ccfa458f35 * Doc 2022-08-19 13:50:17 +05:30
Piyush मिश्रः f0a68406f4 Font size bug fixed 2022-08-19 13:41:10 +05:30
Piyush मिश्रः 658eba6f96 * Doc
* Stable Release
2022-08-19 12:56:44 +05:30
Piyush मिश्रः c6763fca4b * Option to enable and disable line spacing
* Version upgrade
2022-08-19 12:23:20 +05:30
Piyush मिश्रः 90d72f9d4e * Added support for blured box around quotes
* Removed useless ttf and added opensans
* Cleaned global and grouped all fonts together in one function
2022-08-17 20:00:26 +05:30
Piyush मिश्रः 55d394380d 1) Added prefix for exporting images
2) Added support for moving tage in x axis
3) Imporved progress bar in export all
4) changed version to 0.5.0-alpha.1
2022-06-24 09:43:26 +05:30
Piyush मिश्रः 4ac7e1938c Version upgrade 2022-03-29 23:20:12 +05:30
Piyush मिश्रः ae1e61141f Export all redraw fixed 2022-03-29 23:05:52 +05:30
Piyush मिश्रः c1633c7197 redraw fixed 2022-03-29 22:16:50 +05:30
Piyush मिश्रः f351dea81a Color and frame of pregress bar 2022-03-29 21:28:03 +05:30
Piyush मिश्रः 558f558dce Added open export folder 2022-03-29 20:42:02 +05:30
Piyush मिश्रः 43d3a63c7f Changes:
1) Added export all with quotes
2) added delete exports
3) export dir is created if not exists on saving file
2022-03-29 20:24:40 +05:30
Piyush मिश्रः 9c3d4a4c0f version upgrade 2022-03-28 23:20:59 +05:30
Piyush मिश्रः e241ac7ff5 Fixes:
1) image naming format
2) panic message shown at end
4) some messages changed
2022-03-28 23:20:13 +05:30
Piyush मिश्रः 8a194a254f log and crash if draw thread is crashed 2022-03-27 01:10:25 +05:30
Piyush मिश्रः 44a3956a31 macro for rwlock 2022-03-27 00:48:33 +05:30
Piyush मिश्रः 231ab35f7b Formating of code 2022-03-27 00:16:29 +05:30
Piyush मिश्रः 627912f89e dialogs positioning and minimum image size delete dialog 2022-03-27 00:02:19 +05:30
Piyush मिश्रः 6f39a8209a Crop minimum limiy while loading image 2022-03-26 20:55:55 +05:30
Piyush मिश्रः efcf7eae1d Version upgrade 2022-03-26 20:24:09 +05:30
Piyush मिश्रः 4b0466eb7a Minimum and maximum image size limit with config 2022-03-26 20:23:47 +05:30
Piyush मिश्रः c2dd76aef1 Image Size limit 2022-03-26 19:53:59 +05:30
Piyush मिश्रः dc5f7206dc Message and alert dialog from main thread 2022-03-26 19:38:44 +05:30
Piyush मिश्रः 969c867cd4 Version upgrade 2022-03-26 10:03:42 +05:30
Piyush मिश्रः ca60e0e316 export image naming format for delete function
changed name of function
2022-03-26 10:03:14 +05:30
Piyush मिश्रः 2db68a3dc1 Using mozjpeg for jpeg image converisons.
New export image naming format
2022-03-26 01:11:16 +05:30
Piyush मिश्रः fadf6e68e1 Dir name in title of window 2022-03-25 14:09:57 +05:30
Piyush मिश्रः 59aec4ab3b 1) changed name format of prop files
2) Added feature to count number of images with quote and without
2022-03-25 13:57:11 +05:30
Piyush मिश्रः c0b31a9b31 Lots of changes!!!
Note: Still in alpha because we are using git as dependency

Change Log:
1) Added support for webp
2) Added result ext to show error and warning dialogs
3) Image save extension is stored as enum now
4) Mime of image is also stored with the name of image
5) Different mime type images are being loaded with own loaders
2022-03-24 20:28:29 +05:30
Piyush मिश्रः a21f638432 Fixed missing stuffs with image formats 2022-03-05 19:57:26 +05:30
Piyush मिश्रः 3d9b20bbc8 Image export formats 2022-03-05 00:18:57 +05:30
Piyush मिश्रः 7f67d5ad29 Export image with image crate 2022-02-28 19:38:46 +05:30
Piyush मिश्रः ad1aaae2a0 Version upgrade 2022-02-28 17:37:26 +05:30
Piyush मिश्रः 57f79fe556 Export jpeg at 95 quality 2022-02-28 17:36:51 +05:30
Piyush मिश्रः b139ce4c98 Version shift 2022-02-06 15:34:01 +05:30
Piyush मिश्रः 4df87ca333 Fix: Set crop position of image as None id not defined in prop file 2022-02-04 18:14:44 +05:30
Piyush मिश्रः 7aacea2310 Bug fixes:
1) awake all widgets after redraw of image after saving
2) Floting point issue in Crop position of new image
2022-02-04 12:57:00 +05:30
Piyush मिश्रः 50924e9354 Redraw after activate 2022-02-04 02:04:08 +05:30
Piyush मिश्रः cb3c12f708 Logs 2022-01-29 23:47:42 +05:30
24 changed files with 2300 additions and 714 deletions

783
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
[package] [package]
name = "post_maker" name = "post_maker"
version = "0.2.2" version = "0.6.3"
edition = "2021" edition = "2021"
description = "Post Maker helps you to make post for Instagram and other Social Media apps easily." description = "Post Maker helps you to make post for instagram and other social media apps easily and in massive amount. "
authors = ["PiyushXCoder <https://piyushxcoder.in>"] authors = ["PiyushXCoder <https://piyushxcoder.in>"]
license = "GPL-3.0-only" license = "GPL-3.0-only"
readme = "README.md" readme = "README.md"
@ -10,18 +10,28 @@ repository = "https://github.com/PiyushXCoder/post_maker"
keywords = ["posts", "instagram", "facebook", "twitter"] keywords = ["posts", "instagram", "facebook", "twitter"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev]
opt-level = 1
[profile.release]
opt-level = 3
[dependencies] [dependencies]
clap = { version = "3.0", features = ["derive"] } clap = { version = "3.0", features = ["derive"] }
log = "0.4" log = "0.4"
simplelog = "0.11" simplelog = "0.11"
fltk = "1.2" fltk = "1.2"
fltk-theme = "0.4" fltk-theme = "0.4"
image = "0.23" image = "0.24.1"
imageproc = "0.22" imageproc = "0.23"
webp = "0.2"
rusttype = "0.9" rusttype = "0.9"
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
lazy_static = "1.4" lazy_static = "1.4"
dirs = "4.0" dirs = "4.0"
infer = "0.7.0"
textwrap = "0.14" textwrap = "0.14"
webbrowser = "0.5" webbrowser = "0.5"
mozjpeg = "0.9.2"
bichannel = "0.0.4"

2
Cross.toml Normal file
View File

@ -0,0 +1,2 @@
[target.x86_64-pc-windows-gnu]
image = "rustembedded/cross:x86_64-pc-windows-gnu"

View File

@ -4,7 +4,10 @@
<img alt="actix-web-grants" src="./assets/icon_with_text.svg" width="170"> <img alt="actix-web-grants" src="./assets/icon_with_text.svg" width="170">
</p> </p>
> Post Maker helps you to make post for instagram and other social media apps easily. [![Crates.io](https://img.shields.io/crates/v/post_maker?color=%23069060&style=for-the-badge)](https://crates.io/crates/post_maker)
[![Crates.io](https://img.shields.io/crates/l/post_maker?style=for-the-badge)](https://spdx.org/licenses/GPL-3.0-only.html)
> Post Maker helps you to make post for instagram and other social media apps easily and in massive amount.
## Installing ## Installing
@ -41,7 +44,10 @@ You can follow rust's official guide to install rust compiler and cargo [here](h
## General Overview of Controls ## General Overview of Controls
![](assets/20220124_152902_screenshot.png) ![](assets/Screenshot_2022-08-19_13-48-49.png)
## Achievement
The Post Maker is listed in [Official Project Showcase of fltk-rs](https://github.com/fltk-rs/fltk-rs/issues/418)
## License ## License

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 KiB

BIN
assets/OpenSans-Regular.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Binary file not shown.

View File

@ -13,11 +13,10 @@
*/ */
//! About Window //! About Window
use crate::{config, globals}; use crate::{config, globals, result_ext::ResultExt};
use fltk::{ use fltk::{
app, app,
button::Button, button::Button,
dialog,
enums::{self, Align, Event}, enums::{self, Align, Event},
frame::Frame, frame::Frame,
group::Flex, group::Flex,
@ -152,10 +151,7 @@ impl About {
// Repository Link // Repository Link
self.repo_link.handle(|_, ev| { self.repo_link.handle(|_, ev| {
if ev == Event::Push { if ev == Event::Push {
if let Err(e) = webbrowser::open(env!("CARGO_PKG_REPOSITORY")) { webbrowser::open(env!("CARGO_PKG_REPOSITORY")).warn_log("Failed to open the link!");
dialog::alert_default("Failed to open the link!");
warn!("Failed to open the link!\n{:?}", e);
}
} }
true true
}); });
@ -163,10 +159,7 @@ impl About {
// Developer's Link // Developer's Link
self.dev_link.handle(|_, ev| { self.dev_link.handle(|_, ev| {
if ev == Event::Push { if ev == Event::Push {
if let Err(e) = webbrowser::open("https://piyushxcoder.in") { webbrowser::open("https://piyushxcoder.in").warn_log("Failed to open the link!");
dialog::alert_default("Failed to open the link!");
warn!("Failed to open the link!\n{:?}", e);
}
} }
true true
}); });
@ -174,10 +167,8 @@ impl About {
// License Link // License Link
self.license_link.handle(|_, ev| { self.license_link.handle(|_, ev| {
if ev == Event::Push { if ev == Event::Push {
if let Err(e) = webbrowser::open("https://www.gnu.org/licenses/gpl-3.0.html") { webbrowser::open("https://www.gnu.org/licenses/gpl-3.0.html")
dialog::alert_default("Failed to open the link!"); .warn_log("Failed to open the link!");
warn!("Failed to open the link!\n{:?}", e);
}
} }
true true
}); });

View File

@ -13,18 +13,14 @@
*/ */
//! load, save configuration and parse cli args //! load, save configuration and parse cli args
use crate::{config_picker::ConfigPicker, globals}; use crate::{
config_picker::ConfigPicker, dialog, globals, result_ext::ResultExt, utils::ImageType,
};
use clap::{ArgEnum, Parser}; use clap::{ArgEnum, Parser};
use fltk::dialog;
use fltk_theme::ThemeType; use fltk_theme::ThemeType;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{collections::HashMap, fs::File, path::PathBuf};
collections::HashMap,
fs::File,
path::PathBuf,
time::{Duration, SystemTime},
};
lazy_static! { lazy_static! {
/// Directory where all Configurations are present /// Directory where all Configurations are present
@ -129,10 +125,16 @@ pub(crate) struct ConfigFile {
pub(crate) quote_position_ratio: f64, pub(crate) quote_position_ratio: f64,
pub(crate) subquote_position_ratio: f64, pub(crate) subquote_position_ratio: f64,
pub(crate) subquote2_position_ratio: f64, pub(crate) subquote2_position_ratio: f64,
pub(crate) tag_position_ratio: f64, pub(crate) tag_x_position_ratio: f64,
pub(crate) tag_y_position_ratio: f64,
pub(crate) tag2_position_ratio: f64, pub(crate) tag2_position_ratio: f64,
pub(crate) image_ratio: (f64, f64), pub(crate) image_ratio: (f64, f64),
pub(crate) color_layer: [u8; 4], pub(crate) color_layer: [u8; 4],
pub(crate) minimum_width_limit: f64, // for export of image
pub(crate) maximum_width_limit: f64, // for export of image
pub(crate) draw_box_around_quote: bool,
pub(crate) line_spacing: bool,
pub(crate) image_format: ImageType,
} }
impl Default for ConfigFile { impl Default for ConfigFile {
@ -143,7 +145,7 @@ impl Default for ConfigFile {
subquote2_font: String::new(), subquote2_font: String::new(),
tag_font: String::new(), tag_font: String::new(),
tag2_font: String::new(), tag2_font: String::new(),
quote_font_ratio: 230.0, quote_font_ratio: 250.0,
subquote_font_ratio: 230.0, subquote_font_ratio: 230.0,
subquote2_font_ratio: 230.0, subquote2_font_ratio: 230.0,
tag_font_ratio: 150.0, tag_font_ratio: 150.0,
@ -151,10 +153,16 @@ impl Default for ConfigFile {
quote_position_ratio: 0.7, quote_position_ratio: 0.7,
subquote_position_ratio: 0.8, subquote_position_ratio: 0.8,
subquote2_position_ratio: 0.9, subquote2_position_ratio: 0.9,
tag_position_ratio: 0.5, tag_x_position_ratio: 0.95,
tag_y_position_ratio: 0.5,
tag2_position_ratio: 0.95, tag2_position_ratio: 0.95,
image_ratio: (4.0, 5.0), image_ratio: (4.0, 5.0),
color_layer: [20, 22, 25, 197], color_layer: [20, 22, 25, 0],
minimum_width_limit: 650.0,
maximum_width_limit: 1080.0,
draw_box_around_quote: true,
line_spacing: true,
image_format: ImageType::Jpeg,
} }
} }
} }
@ -169,7 +177,7 @@ impl ConfigFile {
None => HashMap::new(), None => HashMap::new(),
}; };
let default_config = (&*globals::CONFIG_NAME.read().unwrap()).to_string(); let default_config = (&*rw_read!(globals::CONFIG_NAME)).to_string();
let config_name = if (map.len() > 1 || !map.contains_key(&default_config)) let config_name = if (map.len() > 1 || !map.contains_key(&default_config))
&& map.len() != 0 && map.len() != 0
{ {
@ -184,7 +192,7 @@ impl ConfigFile {
}; };
if let Some(config) = map.get(&config_name) { if let Some(config) = map.get(&config_name) {
*globals::CONFIG_NAME.write().unwrap() = config_name; *rw_write!(globals::CONFIG_NAME) = config_name;
return config.to_owned(); return config.to_owned();
} }
} }
@ -192,7 +200,7 @@ impl ConfigFile {
let config = Self::default(); let config = Self::default();
let mut configs = HashMap::new(); let mut configs = HashMap::new();
configs.insert( configs.insert(
(&*globals::CONFIG_NAME.read().unwrap()).to_owned(), (&*rw_read!(globals::CONFIG_NAME)).to_owned(),
config.clone(), config.clone(),
); );
save_configs(configs); save_configs(configs);
@ -210,46 +218,16 @@ pub(crate) fn get_configs() -> Option<HashMap<String, ConfigFile>> {
/// Save configs /// Save configs
pub(crate) fn save_configs(configs: HashMap<String, ConfigFile>) { pub(crate) fn save_configs(configs: HashMap<String, ConfigFile>) {
if let Err(e) = std::fs::write(&*CONFIG_FILE, serde_json::to_string(&configs).unwrap()) { std::fs::write(&*CONFIG_FILE, serde_json::to_string(&configs).unwrap())
dialog::alert_default("Can't write config!"); .expect_log("Can't write config!");
error!("Can't write config!\n{:?}", e);
panic!("Can't write config!\n{:?}", e);
}
} }
pub(crate) fn log_file() -> File { pub(crate) fn log_file() -> File {
match File::open(&*LOG_FILE) {
Ok(mut file) => {
if is_file_30_days_old(&file) {
match File::create(&*LOG_FILE) { match File::create(&*LOG_FILE) {
Ok(f) => file = f,
Err(e) => {
dialog::alert_default("Can't open log file!");
panic!("{:?}", e);
}
}
}
file
}
Err(_) => match File::create(&*LOG_FILE) {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(e) => {
dialog::alert_default("Can't open log file!"); dialog::alert_default("Can't open log file!");
panic!("{:?}", e); panic!("{:?}", e);
} }
},
} }
} }
pub(crate) fn is_file_30_days_old(file: &File) -> bool {
if let Ok(meta) = file.metadata() {
if let Ok(time) = meta.created() {
if let Ok(dur) = SystemTime::now().duration_since(time) {
if dur > Duration::from_secs(60 * 60 * 24 * 30) {
return true;
}
}
}
}
false
}

View File

@ -58,7 +58,7 @@ impl ConfigPicker {
let top_padding_btn = Frame::default(); let top_padding_btn = Frame::default();
let mut panel_flex = Flex::default().row(); let mut panel_flex = Flex::default().row();
Frame::default(); Frame::default();
let apply_btn = Button::default().with_label("apply"); let apply_btn = Button::default().with_label("Apply");
Frame::default(); Frame::default();
panel_flex.set_size(&apply_btn, 100); panel_flex.set_size(&apply_btn, 100);
panel_flex.end(); panel_flex.end();

View File

@ -14,27 +14,27 @@
//! Window to edit configuration //! Window to edit configuration
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{
config::{self, ConfigFile},
dialog, globals,
result_ext::ResultExt,
utils::{self, ImageType},
};
use fltk::{ use fltk::{
app, app,
browser::{Browser, BrowserType}, browser::{Browser, BrowserType},
button::Button, button::{Button, CheckButton, RadioRoundButton},
dialog::{self, FileDialogOptions, NativeFileChooser}, dialog::{FileDialogOptions, NativeFileChooser},
enums::{self, Align, Event, Font}, enums::{self, Align, Event, Font},
frame::Frame, frame::Frame,
group::Flex, group::{Flex, Scroll},
image::SvgImage, image::SvgImage,
output::Output, output::Output,
prelude::*, prelude::*,
valuator::ValueInput, valuator::ValueInput,
window::Window, window::Window,
}; };
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::{
config::{self, ConfigFile},
globals, utils,
};
pub(crate) struct ConfigWindow { pub(crate) struct ConfigWindow {
pub(crate) win: Window, pub(crate) win: Window,
@ -60,14 +60,21 @@ pub(crate) struct ConfigWindow {
pub(crate) quote_position_ratio: ValueInput, pub(crate) quote_position_ratio: ValueInput,
pub(crate) subquote_position_ratio: ValueInput, pub(crate) subquote_position_ratio: ValueInput,
pub(crate) subquote2_position_ratio: ValueInput, pub(crate) subquote2_position_ratio: ValueInput,
pub(crate) tag_position_ratio: ValueInput, pub(crate) tag_x_position_ratio: ValueInput,
pub(crate) tag_y_position_ratio: ValueInput,
pub(crate) tag2_position_ratio: ValueInput, pub(crate) tag2_position_ratio: ValueInput,
pub(crate) image_ratio_width: ValueInput, pub(crate) image_ratio_width: ValueInput,
pub(crate) image_ratio_height: ValueInput, pub(crate) image_ratio_height: ValueInput,
pub(crate) draw_box_around_quote: CheckButton,
pub(crate) line_spacing: CheckButton,
pub(crate) minimum_width_limit: ValueInput,
pub(crate) maximum_width_limit: ValueInput,
/// RGB value of top translucent layer /// RGB value of top translucent layer
pub(crate) translucent_layer_rgb: Button, pub(crate) translucent_layer_rgb: Button,
/// opacity value of top translucent layer /// opacity value of top translucent layer
pub(crate) translucent_layer_alpha: ValueInput, pub(crate) translucent_layer_alpha: ValueInput,
pub(crate) png_format: RadioRoundButton,
pub(crate) jpeg_format: RadioRoundButton,
pub(crate) defaults_btn: Button, pub(crate) defaults_btn: Button,
pub(crate) save_btn: Button, pub(crate) save_btn: Button,
pub(crate) cancel_btn: Button, pub(crate) cancel_btn: Button,
@ -78,12 +85,16 @@ pub(crate) struct ConfigWindow {
impl ConfigWindow { impl ConfigWindow {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let configs = config::get_configs().unwrap_or(HashMap::new()); let configs = config::get_configs().unwrap_or(HashMap::new());
let mut win = Window::new(0, 0, 900, 680, "Config").center_screen(); let mut win = Window::new(0, 0, 900, 600, "Config").center_screen();
win.set_icon(Some( win.set_icon(Some(
SvgImage::from_data(globals::ICON.to_str().unwrap()).unwrap(), SvgImage::from_data(globals::ICON.to_str().unwrap()).unwrap(),
)); ));
let mut row = Flex::default().with_size(890, 670).with_pos(5, 5).row();
let mut config_picker_flex = Flex::default().column(); // Config picking area
let mut config_picker_flex = Flex::default()
.with_size(200, win.height() - 50)
.with_pos(5, 5)
.column();
// Picker // Picker
let browse = Browser::default().with_type(BrowserType::Hold); let browse = Browser::default().with_type(BrowserType::Hold);
@ -103,9 +114,30 @@ impl ConfigWindow {
config_picker_flex.set_size(&panel_flex, 30); config_picker_flex.set_size(&panel_flex, 30);
config_picker_flex.set_size(&bottom_padding_btn, 5); config_picker_flex.set_size(&bottom_padding_btn, 5);
config_picker_flex.end(); config_picker_flex.end();
row.set_size(&config_picker_flex, 200);
let mut col = Flex::default().column(); // Bottom Panel
let mut panel_grp = Flex::default()
.with_size(win.width() - 10, 30)
.with_pos(5, win.height() - 40)
.row();
Frame::default();
let defaults_btn = Button::default().with_label("Defaults");
let save_btn = Button::default().with_label("Save");
let cancel_btn = Button::default().with_label("Cancel");
panel_grp.set_size(&defaults_btn, 100);
panel_grp.set_size(&save_btn, 100);
panel_grp.set_size(&cancel_btn, 100);
panel_grp.end();
// Rest everything
let mut scroll = Scroll::default()
.with_size(win.width() - 210, win.height() - 50)
.with_pos(205, 5);
let mut col = Flex::default()
.with_size(scroll.width() - 35, 850)
.column()
.with_pos(100, 0);
let mut label = Frame::default().with_label("Fonts:"); let mut label = Frame::default().with_label("Fonts:");
label.set_label_font(enums::Font::HelveticaBold); label.set_label_font(enums::Font::HelveticaBold);
@ -188,7 +220,7 @@ impl ConfigWindow {
let mut label = Frame::default().with_label("Ratio of size of text:"); let mut label = Frame::default().with_label("Ratio of size of text:");
label.set_label_font(enums::Font::HelveticaBold); label.set_label_font(enums::Font::HelveticaBold);
col.set_size(&label, 15); col.set_size(&label, 15);
let mut hint = Frame::default().with_label("Font size in image of resolution 4000x5000"); let mut hint = Frame::default().with_label("Font size in image of height 4000 pixels");
hint.set_label_font(Font::CourierItalic); hint.set_label_font(Font::CourierItalic);
hint.set_label_size(12); hint.set_label_size(12);
col.set_size(&hint, 20); col.set_size(&hint, 20);
@ -306,16 +338,27 @@ impl ConfigWindow {
// column 2 // column 2
let mut col_grp = Flex::default().column(); let mut col_grp = Flex::default().column();
let mut tag_position_ratio_grp = Flex::default().row(); let mut tag_x_position_ratio_grp = Flex::default().row();
tag_position_ratio_grp.set_size( tag_x_position_ratio_grp.set_size(
&Frame::default() &Frame::default()
.with_label("Tag") .with_label("Tag (x)")
.with_align(Align::Right | Align::Inside), .with_align(Align::Right | Align::Inside),
130, 130,
); );
let tag_position_ratio = ValueInput::default(); let tag_x_position_ratio = ValueInput::default();
tag_position_ratio_grp.end(); tag_x_position_ratio_grp.end();
col_grp.set_size(&tag_position_ratio_grp, 30); col_grp.set_size(&tag_x_position_ratio_grp, 30);
let mut tag_y_position_ratio_grp = Flex::default().row();
tag_y_position_ratio_grp.set_size(
&Frame::default()
.with_label("Tag (y)")
.with_align(Align::Right | Align::Inside),
130,
);
let tag_y_position_ratio = ValueInput::default();
tag_y_position_ratio_grp.end();
col_grp.set_size(&tag_y_position_ratio_grp, 30);
let mut tag2_position_ratio_grp = Flex::default().row(); let mut tag2_position_ratio_grp = Flex::default().row();
tag2_position_ratio_grp.set_size( tag2_position_ratio_grp.set_size(
@ -348,6 +391,60 @@ impl ConfigWindow {
image_ratio_grp.end(); image_ratio_grp.end();
col.set_size(&image_ratio_grp, 30); col.set_size(&image_ratio_grp, 30);
// Draw box around Quote
let mut label = Frame::default().with_label("Quote Special:");
label.set_label_font(enums::Font::HelveticaBold);
col.set_size(&label, 15);
let mut quote_special_flex = Flex::default().row();
quote_special_flex.set_size(&Frame::default(), 20);
let mut draw_box_around_quote = CheckButton::default().with_label("Draw box around text");
draw_box_around_quote.set_value(true);
let mut line_spacing = CheckButton::default().with_label("Line spacing in quotes");
line_spacing.set_value(true);
quote_special_flex.end();
col.set_size(&quote_special_flex, 30);
let mut label = Frame::default().with_label("Image with limits:");
label.set_label_font(enums::Font::HelveticaBold);
col.set_size(&label, 15);
let mut hint = Frame::default().with_label("Limiting width of image in pixels");
hint.set_label_font(Font::CourierItalic);
hint.set_label_size(12);
col.set_size(&hint, 20);
// Size Ratio Group
let row_grp = Flex::default().row();
// column 1
let mut col_grp = Flex::default().column();
let mut minimum_width_limit_grp = Flex::default().row();
minimum_width_limit_grp.set_size(
&Frame::default()
.with_label("Minimum")
.with_align(Align::Right | Align::Inside),
130,
);
let minimum_width_limit = ValueInput::default();
minimum_width_limit_grp.end();
col_grp.set_size(&minimum_width_limit_grp, 30);
col_grp.end();
// column 2
let mut col_grp = Flex::default().column();
let mut maximum_width_limit_grp = Flex::default().row();
maximum_width_limit_grp.set_size(
&Frame::default()
.with_label("Maximim")
.with_align(Align::Right | Align::Inside),
130,
);
let maximum_width_limit = ValueInput::default();
maximum_width_limit_grp.end();
col_grp.set_size(&maximum_width_limit_grp, 30);
col_grp.end();
row_grp.end();
col.set_size(&row_grp, 40);
let mut label = Frame::default().with_label("Colour for dark layer:"); let mut label = Frame::default().with_label("Colour for dark layer:");
label.set_label_font(enums::Font::HelveticaBold); label.set_label_font(enums::Font::HelveticaBold);
col.set_size(&label, 15); col.set_size(&label, 15);
@ -358,7 +455,7 @@ impl ConfigWindow {
col.set_size(&hint, 20); col.set_size(&hint, 20);
let mut translucent_layer_flex = Flex::default().row(); let mut translucent_layer_flex = Flex::default().row();
translucent_layer_flex.set_pad(2); translucent_layer_flex.set_size(&Frame::default(), 20);
translucent_layer_flex.set_size(&Frame::default().with_label("Colour"), 50); translucent_layer_flex.set_size(&Frame::default().with_label("Colour"), 50);
let mut translucent_layer_rgb = Button::default(); let mut translucent_layer_rgb = Button::default();
translucent_layer_rgb.set_frame(enums::FrameType::BorderBox); translucent_layer_rgb.set_frame(enums::FrameType::BorderBox);
@ -368,25 +465,32 @@ impl ConfigWindow {
translucent_layer_flex.end(); translucent_layer_flex.end();
col.set_size(&translucent_layer_flex, 30); col.set_size(&translucent_layer_flex, 30);
let mut label = Frame::default().with_label("Export Format:");
label.set_label_font(enums::Font::HelveticaBold);
col.set_size(&label, 15);
let mut hint = Frame::default().with_label("Image format to export image");
hint.set_label_font(Font::CourierItalic);
hint.set_label_size(12);
col.set_size(&hint, 20);
let mut image_format_flex = Flex::default().row();
image_format_flex.set_size(&Frame::default(), 20);
let mut png_format = RadioRoundButton::default().with_label("Png");
png_format.set_value(true);
let jpeg_format = RadioRoundButton::default().with_label("Jpeg");
image_format_flex.end();
col.set_size(&image_format_flex, 30);
Frame::default(); Frame::default();
let mut panel_grp = Flex::default().row();
Frame::default();
let defaults_btn = Button::default().with_label("Defaults");
let save_btn = Button::default().with_label("Save");
let cancel_btn = Button::default().with_label("Cancel");
panel_grp.set_size(&defaults_btn, 100);
panel_grp.set_size(&save_btn, 100);
panel_grp.set_size(&cancel_btn, 100);
panel_grp.end();
col.set_size(&panel_grp, 30);
col.end(); col.end();
row.end();
scroll.end();
scroll.make_resizable(true);
scroll.scroll_to(-1 * col.x() - 5, -1 * col.y() - 5);
win.end(); win.end();
win.make_modal(true); win.make_modal(true);
win.make_resizable(true);
let mut config_window = Self { let mut config_window = Self {
win, win,
@ -412,12 +516,19 @@ impl ConfigWindow {
quote_position_ratio, quote_position_ratio,
subquote_position_ratio, subquote_position_ratio,
subquote2_position_ratio, subquote2_position_ratio,
tag_position_ratio, tag_x_position_ratio,
tag_y_position_ratio,
tag2_position_ratio, tag2_position_ratio,
image_ratio_width, image_ratio_width,
image_ratio_height, image_ratio_height,
draw_box_around_quote,
line_spacing,
minimum_width_limit,
maximum_width_limit,
translucent_layer_rgb, translucent_layer_rgb,
translucent_layer_alpha, translucent_layer_alpha,
png_format,
jpeg_format,
defaults_btn, defaults_btn,
save_btn, save_btn,
cancel_btn, cancel_btn,
@ -431,7 +542,7 @@ impl ConfigWindow {
// Show to edit config // Show to edit config
pub(crate) fn show(&mut self) -> bool { pub(crate) fn show(&mut self) -> bool {
let config_name = &*globals::CONFIG_NAME.read().unwrap(); let config_name = &*rw_read!(globals::CONFIG_NAME);
self.browse.clear(); self.browse.clear();
for (idx, name) in self.configs.borrow().keys().enumerate() { for (idx, name) in self.configs.borrow().keys().enumerate() {
self.browse.add(name); self.browse.add(name);
@ -440,7 +551,7 @@ impl ConfigWindow {
} }
} }
*self.selected_browse_line.borrow_mut() = self.browse.value(); *self.selected_browse_line.borrow_mut() = self.browse.value();
let config = globals::CONFIG.read().unwrap(); let config = rw_read!(globals::CONFIG);
self.quote_font.set_value(config.quote_font.as_str()); self.quote_font.set_value(config.quote_font.as_str());
self.subquote_font.set_value(config.subquote_font.as_str()); self.subquote_font.set_value(config.subquote_font.as_str());
self.subquote2_font self.subquote2_font
@ -460,14 +571,37 @@ impl ConfigWindow {
.set_value(config.subquote_position_ratio); .set_value(config.subquote_position_ratio);
self.subquote2_position_ratio self.subquote2_position_ratio
.set_value(config.subquote2_position_ratio); .set_value(config.subquote2_position_ratio);
self.tag_position_ratio.set_value(config.tag_position_ratio); self.tag_x_position_ratio
.set_value(config.tag_x_position_ratio);
self.tag_y_position_ratio
.set_value(config.tag_y_position_ratio);
self.tag2_position_ratio self.tag2_position_ratio
.set_value(config.tag2_position_ratio); .set_value(config.tag2_position_ratio);
self.image_ratio_width.set_value(config.image_ratio.0); self.image_ratio_width.set_value(config.image_ratio.0);
self.image_ratio_height.set_value(config.image_ratio.1); self.image_ratio_height.set_value(config.image_ratio.1);
self.draw_box_around_quote
.set_checked(config.draw_box_around_quote);
self.line_spacing.set_checked(config.line_spacing);
self.minimum_width_limit
.set_value(config.minimum_width_limit);
self.maximum_width_limit
.set_value(config.maximum_width_limit);
utils::set_color_btn_rgba(config.color_layer, &mut self.translucent_layer_rgb); utils::set_color_btn_rgba(config.color_layer, &mut self.translucent_layer_rgb);
self.translucent_layer_alpha self.translucent_layer_alpha
.set_value(config.color_layer[3] as f64); .set_value(config.color_layer[3] as f64);
match config.image_format {
utils::ImageType::Png => {
self.png_format.set_value(true);
self.jpeg_format.set_value(false)
}
utils::ImageType::Jpeg => {
self.png_format.set_value(false);
self.jpeg_format.set_value(true)
}
_ => (),
}
*self.did_save.borrow_mut() = false; *self.did_save.borrow_mut() = false;
drop(config); drop(config);
self.win.show(); self.win.show();
@ -493,10 +627,15 @@ impl ConfigWindow {
let mut quote_position_ratio = self.quote_position_ratio.clone(); let mut quote_position_ratio = self.quote_position_ratio.clone();
let mut subquote_position_ratio = self.subquote_position_ratio.clone(); let mut subquote_position_ratio = self.subquote_position_ratio.clone();
let mut subquote2_position_ratio = self.subquote2_position_ratio.clone(); let mut subquote2_position_ratio = self.subquote2_position_ratio.clone();
let mut tag_position_ratio = self.tag_position_ratio.clone(); let mut tag_x_position_ratio = self.tag_x_position_ratio.clone();
let mut tag_y_position_ratio = self.tag_y_position_ratio.clone();
let mut tag2_position_ratio = self.tag2_position_ratio.clone(); let mut tag2_position_ratio = self.tag2_position_ratio.clone();
let mut image_ratio_width = self.image_ratio_width.clone(); let mut image_ratio_width = self.image_ratio_width.clone();
let mut image_ratio_height = self.image_ratio_height.clone(); let mut image_ratio_height = self.image_ratio_height.clone();
let draw_box_around_quote = self.draw_box_around_quote.clone();
let line_spacing = self.line_spacing.clone();
let mut minimum_width_limit = self.minimum_width_limit.clone();
let mut maximum_width_limit = self.maximum_width_limit.clone();
let mut layer_rgb = self.translucent_layer_rgb.clone(); let mut layer_rgb = self.translucent_layer_rgb.clone();
let mut layer_alpha = self.translucent_layer_alpha.clone(); let mut layer_alpha = self.translucent_layer_alpha.clone();
let mut browse = self.browse.clone(); let mut browse = self.browse.clone();
@ -536,10 +675,15 @@ impl ConfigWindow {
quote_position_ratio.set_value(conf.quote_position_ratio); quote_position_ratio.set_value(conf.quote_position_ratio);
subquote_position_ratio.set_value(conf.subquote_position_ratio); subquote_position_ratio.set_value(conf.subquote_position_ratio);
subquote2_position_ratio.set_value(conf.subquote2_position_ratio); subquote2_position_ratio.set_value(conf.subquote2_position_ratio);
tag_position_ratio.set_value(conf.tag_position_ratio); tag_x_position_ratio.set_value(conf.tag_x_position_ratio);
tag_y_position_ratio.set_value(conf.tag_y_position_ratio);
tag2_position_ratio.set_value(conf.tag2_position_ratio); tag2_position_ratio.set_value(conf.tag2_position_ratio);
image_ratio_width.set_value(conf.image_ratio.0); image_ratio_width.set_value(conf.image_ratio.0);
image_ratio_height.set_value(conf.image_ratio.1); image_ratio_height.set_value(conf.image_ratio.1);
draw_box_around_quote.set_checked(conf.draw_box_around_quote);
line_spacing.set_checked(conf.line_spacing);
minimum_width_limit.set_value(conf.minimum_width_limit);
maximum_width_limit.set_value(conf.maximum_width_limit);
utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb); utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb);
layer_alpha.set_value(conf.color_layer[3] as f64); layer_alpha.set_value(conf.color_layer[3] as f64);
browse.add(&name); browse.add(&name);
@ -563,17 +707,22 @@ impl ConfigWindow {
let mut quote_position_ratio = self.quote_position_ratio.clone(); let mut quote_position_ratio = self.quote_position_ratio.clone();
let mut subquote_position_ratio = self.subquote_position_ratio.clone(); let mut subquote_position_ratio = self.subquote_position_ratio.clone();
let mut subquote2_position_ratio = self.subquote2_position_ratio.clone(); let mut subquote2_position_ratio = self.subquote2_position_ratio.clone();
let mut tag_position_ratio = self.tag_position_ratio.clone(); let mut tag_x_position_ratio = self.tag_x_position_ratio.clone();
let mut tag_y_position_ratio = self.tag_y_position_ratio.clone();
let mut tag2_position_ratio = self.tag2_position_ratio.clone(); let mut tag2_position_ratio = self.tag2_position_ratio.clone();
let mut image_ratio_width = self.image_ratio_width.clone(); let mut image_ratio_width = self.image_ratio_width.clone();
let mut image_ratio_height = self.image_ratio_height.clone(); let mut image_ratio_height = self.image_ratio_height.clone();
let draw_box_around_quote = self.draw_box_around_quote.clone();
let line_spacing = self.line_spacing.clone();
let mut minimum_width_limit = self.minimum_width_limit.clone();
let mut maximum_width_limit = self.maximum_width_limit.clone();
let mut layer_rgb = self.translucent_layer_rgb.clone(); let mut layer_rgb = self.translucent_layer_rgb.clone();
let mut layer_alpha = self.translucent_layer_alpha.clone(); let mut layer_alpha = self.translucent_layer_alpha.clone();
let mut browse = self.browse.clone(); let mut browse = self.browse.clone();
let configs = Rc::clone(&self.configs); let configs = Rc::clone(&self.configs);
let selected_browse_line = Rc::clone(&self.selected_browse_line); let selected_browse_line = Rc::clone(&self.selected_browse_line);
self.del_config_btn.set_callback(move |_| { self.del_config_btn.set_callback(move |_| {
let ch = dialog::choice_default("Do you want to delete??", "Yes", "No", ""); let ch = dialog::choice_default("Do you want to delete??", "Yes", "No");
if ch == 1 { if ch == 1 {
return; return;
} }
@ -605,10 +754,15 @@ impl ConfigWindow {
quote_position_ratio.set_value(conf.quote_position_ratio); quote_position_ratio.set_value(conf.quote_position_ratio);
subquote_position_ratio.set_value(conf.subquote_position_ratio); subquote_position_ratio.set_value(conf.subquote_position_ratio);
subquote2_position_ratio.set_value(conf.subquote2_position_ratio); subquote2_position_ratio.set_value(conf.subquote2_position_ratio);
tag_position_ratio.set_value(conf.tag_position_ratio); tag_x_position_ratio.set_value(conf.tag_x_position_ratio);
tag_y_position_ratio.set_value(conf.tag_y_position_ratio);
tag2_position_ratio.set_value(conf.tag2_position_ratio); tag2_position_ratio.set_value(conf.tag2_position_ratio);
image_ratio_width.set_value(conf.image_ratio.0); image_ratio_width.set_value(conf.image_ratio.0);
image_ratio_height.set_value(conf.image_ratio.1); image_ratio_height.set_value(conf.image_ratio.1);
draw_box_around_quote.set_checked(conf.draw_box_around_quote);
line_spacing.set_checked(conf.line_spacing);
minimum_width_limit.set_value(conf.minimum_width_limit);
maximum_width_limit.set_value(conf.maximum_width_limit);
utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb); utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb);
layer_alpha.set_value(conf.color_layer[3] as f64); layer_alpha.set_value(conf.color_layer[3] as f64);
layer_rgb.redraw(); layer_rgb.redraw();
@ -629,10 +783,15 @@ impl ConfigWindow {
let mut quote_position_ratio = self.quote_position_ratio.clone(); let mut quote_position_ratio = self.quote_position_ratio.clone();
let mut subquote_position_ratio = self.subquote_position_ratio.clone(); let mut subquote_position_ratio = self.subquote_position_ratio.clone();
let mut subquote2_position_ratio = self.subquote2_position_ratio.clone(); let mut subquote2_position_ratio = self.subquote2_position_ratio.clone();
let mut tag_position_ratio = self.tag_position_ratio.clone(); let mut tag_x_position_ratio = self.tag_x_position_ratio.clone();
let mut tag_y_position_ratio = self.tag_y_position_ratio.clone();
let mut tag2_position_ratio = self.tag2_position_ratio.clone(); let mut tag2_position_ratio = self.tag2_position_ratio.clone();
let mut image_ratio_width = self.image_ratio_width.clone(); let mut image_ratio_width = self.image_ratio_width.clone();
let mut image_ratio_height = self.image_ratio_height.clone(); let mut image_ratio_height = self.image_ratio_height.clone();
let draw_box_around_quote = self.draw_box_around_quote.clone();
let line_spacing = self.line_spacing.clone();
let mut minimum_width_limit = self.minimum_width_limit.clone();
let mut maximum_width_limit = self.maximum_width_limit.clone();
let mut layer_rgb = self.translucent_layer_rgb.clone(); let mut layer_rgb = self.translucent_layer_rgb.clone();
let mut layer_alpha = self.translucent_layer_alpha.clone(); let mut layer_alpha = self.translucent_layer_alpha.clone();
let configs = Rc::clone(&self.configs); let configs = Rc::clone(&self.configs);
@ -661,10 +820,15 @@ impl ConfigWindow {
quote_position_ratio.set_value(conf.quote_position_ratio); quote_position_ratio.set_value(conf.quote_position_ratio);
subquote_position_ratio.set_value(conf.subquote_position_ratio); subquote_position_ratio.set_value(conf.subquote_position_ratio);
subquote2_position_ratio.set_value(conf.subquote2_position_ratio); subquote2_position_ratio.set_value(conf.subquote2_position_ratio);
tag_position_ratio.set_value(conf.tag_position_ratio); tag_x_position_ratio.set_value(conf.tag_x_position_ratio);
tag_y_position_ratio.set_value(conf.tag_y_position_ratio);
tag2_position_ratio.set_value(conf.tag2_position_ratio); tag2_position_ratio.set_value(conf.tag2_position_ratio);
image_ratio_width.set_value(conf.image_ratio.0); image_ratio_width.set_value(conf.image_ratio.0);
image_ratio_height.set_value(conf.image_ratio.1); image_ratio_height.set_value(conf.image_ratio.1);
draw_box_around_quote.set_checked(conf.draw_box_around_quote);
line_spacing.set_checked(conf.line_spacing);
minimum_width_limit.set_value(conf.minimum_width_limit);
maximum_width_limit.set_value(conf.maximum_width_limit);
utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb); utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb);
layer_alpha.set_value(conf.color_layer[3] as f64); layer_alpha.set_value(conf.color_layer[3] as f64);
layer_rgb.redraw(); layer_rgb.redraw();
@ -902,16 +1066,31 @@ impl ConfigWindow {
true true
}); });
// Tag position ratio // Tag x position ratio
let browse = self.browse.clone(); let browse = self.browse.clone();
let configs = Rc::clone(&self.configs); let configs = Rc::clone(&self.configs);
self.tag_position_ratio.handle(move |f, ev| { self.tag_x_position_ratio.handle(move |f, ev| {
if ev == Event::KeyUp { if ev == Event::KeyUp {
if let Some(conf) = configs if let Some(conf) = configs
.borrow_mut() .borrow_mut()
.get_mut(&browse.selected_text().unwrap()) .get_mut(&browse.selected_text().unwrap())
{ {
conf.tag_position_ratio = f.value(); conf.tag_x_position_ratio = f.value();
}
}
true
});
// Tag y position ratio
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.tag_y_position_ratio.handle(move |f, ev| {
if ev == Event::KeyUp {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.tag_y_position_ratio = f.value();
} }
} }
true true
@ -962,6 +1141,62 @@ impl ConfigWindow {
true true
}); });
// Draw box around quote
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.draw_box_around_quote.handle(move |f, _| {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.draw_box_around_quote = f.value();
}
true
});
// line spacing
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.line_spacing.handle(move |f, _| {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.line_spacing = f.value();
}
true
});
// Minimum Width Limit
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.minimum_width_limit.handle(move |f, ev| {
if ev == Event::KeyUp {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.minimum_width_limit = f.value();
}
}
true
});
// Maximim Width Limit
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.maximum_width_limit.handle(move |f, ev| {
if ev == Event::KeyUp {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.maximum_width_limit = f.value();
}
}
true
});
// Translucent Layer RGB // Translucent Layer RGB
let browse = self.browse.clone(); let browse = self.browse.clone();
let configs = Rc::clone(&self.configs); let configs = Rc::clone(&self.configs);
@ -1006,6 +1241,30 @@ impl ConfigWindow {
true true
}); });
// Png Image format
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.png_format.set_callback(move |_| {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.image_format = ImageType::Png;
}
});
// Jpeg Image format
let browse = self.browse.clone();
let configs = Rc::clone(&self.configs);
self.jpeg_format.set_callback(move |_| {
if let Some(conf) = configs
.borrow_mut()
.get_mut(&browse.selected_text().unwrap())
{
conf.image_format = ImageType::Jpeg;
}
});
// Reset to default configuation button // Reset to default configuation button
let mut quote_font = self.quote_font.clone(); let mut quote_font = self.quote_font.clone();
let mut subquote_font = self.subquote_font.clone(); let mut subquote_font = self.subquote_font.clone();
@ -1020,12 +1279,19 @@ impl ConfigWindow {
let mut quote_position_ratio = self.quote_position_ratio.clone(); let mut quote_position_ratio = self.quote_position_ratio.clone();
let mut subquote_position_ratio = self.subquote_position_ratio.clone(); let mut subquote_position_ratio = self.subquote_position_ratio.clone();
let mut subquote2_position_ratio = self.subquote2_position_ratio.clone(); let mut subquote2_position_ratio = self.subquote2_position_ratio.clone();
let mut tag_position_ratio = self.tag_position_ratio.clone(); let mut tag_x_position_ratio = self.tag_x_position_ratio.clone();
let mut tag_y_position_ratio = self.tag_y_position_ratio.clone();
let mut tag2_position_ratio = self.tag2_position_ratio.clone(); let mut tag2_position_ratio = self.tag2_position_ratio.clone();
let mut image_ratio_width = self.image_ratio_width.clone(); let mut image_ratio_width = self.image_ratio_width.clone();
let mut image_ratio_height = self.image_ratio_height.clone(); let mut image_ratio_height = self.image_ratio_height.clone();
let draw_box_around_quote = self.draw_box_around_quote.clone();
let line_spacing = self.line_spacing.clone();
let mut minimum_width_limit = self.minimum_width_limit.clone();
let mut maximum_width_limit = self.maximum_width_limit.clone();
let mut layer_rgb = self.translucent_layer_rgb.clone(); let mut layer_rgb = self.translucent_layer_rgb.clone();
let mut layer_alpha = self.translucent_layer_alpha.clone(); let mut layer_alpha = self.translucent_layer_alpha.clone();
let mut png_format = self.png_format.clone();
let mut jpeg_format = self.jpeg_format.clone();
let configs = Rc::clone(&self.configs); let configs = Rc::clone(&self.configs);
let browse = self.browse.clone(); let browse = self.browse.clone();
self.defaults_btn.set_callback(move |_| { self.defaults_btn.set_callback(move |_| {
@ -1043,13 +1309,29 @@ impl ConfigWindow {
quote_position_ratio.set_value(conf.quote_position_ratio); quote_position_ratio.set_value(conf.quote_position_ratio);
subquote_position_ratio.set_value(conf.subquote_position_ratio); subquote_position_ratio.set_value(conf.subquote_position_ratio);
subquote2_position_ratio.set_value(conf.subquote2_position_ratio); subquote2_position_ratio.set_value(conf.subquote2_position_ratio);
tag_position_ratio.set_value(conf.tag_position_ratio); tag_x_position_ratio.set_value(conf.tag_x_position_ratio);
tag_y_position_ratio.set_value(conf.tag_y_position_ratio);
tag2_position_ratio.set_value(conf.tag2_position_ratio); tag2_position_ratio.set_value(conf.tag2_position_ratio);
image_ratio_width.set_value(conf.image_ratio.0); image_ratio_width.set_value(conf.image_ratio.0);
image_ratio_height.set_value(conf.image_ratio.1); image_ratio_height.set_value(conf.image_ratio.1);
draw_box_around_quote.set_checked(conf.draw_box_around_quote);
line_spacing.set_checked(conf.line_spacing);
minimum_width_limit.set_value(conf.minimum_width_limit);
maximum_width_limit.set_value(conf.maximum_width_limit);
utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb); utils::set_color_btn_rgba(conf.color_layer, &mut layer_rgb);
layer_rgb.redraw(); layer_rgb.redraw();
layer_alpha.set_value(conf.color_layer[3] as f64); layer_alpha.set_value(conf.color_layer[3] as f64);
match conf.image_format {
ImageType::Png => {
png_format.set_value(true);
jpeg_format.set_value(false);
}
ImageType::Jpeg => {
png_format.set_value(false);
jpeg_format.set_value(true);
}
_ => {}
}
configs configs
.borrow_mut() .borrow_mut()
.insert(browse.selected_text().unwrap(), conf); .insert(browse.selected_text().unwrap(), conf);
@ -1062,8 +1344,8 @@ impl ConfigWindow {
self.save_btn.set_callback(move |_| { self.save_btn.set_callback(move |_| {
config::save_configs((*configs.borrow()).clone()); config::save_configs((*configs.borrow()).clone());
if let Some(c) = configs.borrow().get(&*globals::CONFIG_NAME.read().unwrap()) { if let Some(c) = configs.borrow().get(&*rw_read!(globals::CONFIG_NAME)) {
*globals::CONFIG.write().unwrap() = c.to_owned(); *rw_write!(globals::CONFIG) = c.to_owned();
} }
*did_save.borrow_mut() = true; *did_save.borrow_mut() = true;
win.hide(); win.hide();

View File

@ -15,7 +15,8 @@
//! Window to change Crop properties of image //! Window to change Crop properties of image
use crate::{ use crate::{
globals, globals,
utils::{self, Coord, ImageContainer, ImageProperties}, result_ext::ResultExt,
utils::{self, Coord, ImageContainer, ImageInfo, ImageProperties},
}; };
use fltk::{ use fltk::{
app, button::Button, draw, enums::Event, frame::Frame, group::Flex, image::SvgImage, app, button::Button, draw, enums::Event, frame::Frame, group::Flex, image::SvgImage,
@ -24,7 +25,6 @@ use fltk::{
use image::GenericImageView; use image::GenericImageView;
use std::{ use std::{
cell::RefCell, cell::RefCell,
path::PathBuf,
rc::Rc, rc::Rc,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -108,13 +108,13 @@ impl CropWindow {
/// Call it to show window to crop image /// Call it to show window to crop image
pub(crate) fn load_to_crop( pub(crate) fn load_to_crop(
&mut self, &mut self,
path: &PathBuf, path: &ImageInfo,
crop_pos: Option<(f64, f64)>, crop_pos: Option<(f64, f64)>,
) -> Option<(f64, f64)> { ) -> Option<(f64, f64)> {
let mut container = let mut container =
ImageContainer::new(path, Arc::new(RwLock::new(ImageProperties::default()))); ImageContainer::new(path, Arc::new(RwLock::new(ImageProperties::default())));
{ {
let prop = &mut container.properties.write().unwrap(); let prop = &mut rw_write!(container.properties);
prop.dimension = prop.original_dimension; prop.dimension = prop.original_dimension;
prop.crop_position = match crop_pos { prop.crop_position = match crop_pos {
Some(a) => Some(a), Some(a) => Some(a),
@ -146,7 +146,7 @@ impl CropWindow {
} }
if let Some(cont) = &*self.container.borrow() { if let Some(cont) = &*self.container.borrow() {
cont.properties.read().unwrap().crop_position rw_read!(cont.properties).crop_position
} else { } else {
None None
} }
@ -169,7 +169,7 @@ impl CropWindow {
) )
.unwrap(); .unwrap();
let prop = cont.properties.read().unwrap(); let prop = rw_read!(cont.properties);
let (original_width, original_height) = prop.original_dimension; let (original_width, original_height) = prop.original_dimension;
let (original_x, original_y) = prop.crop_position.unwrap(); let (original_x, original_y) = prop.crop_position.unwrap();
let (resized_width, resized_height) = (image.width() as f64, image.height() as f64); let (resized_width, resized_height) = (image.width() as f64, image.height() as f64);
@ -201,7 +201,7 @@ impl CropWindow {
if let Some(cont) = &*container.borrow_mut() { if let Some(cont) = &*container.borrow_mut() {
let image = &cont.buffer; let image = &cont.buffer;
let mut prop = cont.properties.write().unwrap(); let mut prop = rw_write!(cont.properties);
let (original_x, original_y) = match prop.crop_position { let (original_x, original_y) = match prop.crop_position {
Some(v) => v, Some(v) => v,
@ -272,7 +272,7 @@ impl CropWindow {
let container = Rc::clone(&self.container); let container = Rc::clone(&self.container);
self.win.set_callback(move |f| { self.win.set_callback(move |f| {
if let Some(cont) = &*container.borrow_mut() { if let Some(cont) = &*container.borrow_mut() {
cont.properties.write().unwrap().crop_position = None; rw_write!(cont.properties).crop_position = None;
} }
f.hide(); f.hide();
}); });

36
src/dialog.rs Normal file
View File

@ -0,0 +1,36 @@
/*
This file is part of Post Maker.
Post Maker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Post Maker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Post Maker. If not, see <https://www.gnu.org/licenses/>
*/
pub(crate) use fltk::dialog::{color_chooser_with_default, ColorMode};
use fltk::{app::get_mouse, dialog};
pub(crate) fn input_default(txt: &str, deflt: &str) -> Option<String> {
let (x, y) = get_mouse();
dialog::input(x, y, txt, deflt)
}
pub(crate) fn alert_default(txt: &str) {
let (x, y) = get_mouse();
dialog::alert(x, y, txt)
}
pub(crate) fn message_default(txt: &str) {
let (x, y) = get_mouse();
dialog::message(x, y, txt)
}
pub(crate) fn choice_default(txt: &str, b0: &str, b1: &str) -> i32 {
let (x, y) = get_mouse();
dialog::choice2(x, y, txt, b0, b1, "").unwrap_or(-1)
}

View File

@ -14,16 +14,17 @@
//! Thread to manage drawing in background //! Thread to manage drawing in background
use crate::utils::{ImageContainer, ImageProperties};
use crate::{ use crate::{
globals,
main_window::{MainWindow, Page}, main_window::{MainWindow, Page},
utils::{self, ImagePropertiesFile}, result_ext::ResultExt,
utils::{self, ImageContainer, ImageInfo, ImageProperties, ImagePropertiesFile},
AppMessage, AppMessage,
}; };
use fltk::{ use fltk::{
app, app,
button::Button, button::Button,
dialog, enums, enums,
frame::Frame, frame::Frame,
input::{Input, MultilineInput}, input::{Input, MultilineInput},
menu, menu,
@ -33,7 +34,6 @@ use fltk::{
}; };
use std::{ use std::{
fs, fs,
path::{Path, PathBuf},
sync::{mpsc, Arc, RwLock}, sync::{mpsc, Arc, RwLock},
}; };
@ -53,6 +53,10 @@ pub(crate) enum DrawMessage {
Clone, Clone,
/// Delete file /// Delete file
Delete, Delete,
/// Show details about images linke count of quotes
ShowImagesDetails,
/// Check If image is proper
CheckImage,
} }
/// Spawn thread to manage all actions related to image, like: edit, save, delete /// Spawn thread to manage all actions related to image, like: edit, save, delete
@ -64,6 +68,7 @@ pub(crate) fn spawn_image_thread(
) { ) {
let mut win = main_win.win.clone(); let mut win = main_win.win.clone();
let mut file_choice = main_win.file_choice.clone(); let mut file_choice = main_win.file_choice.clone();
let mut name_prefix = main_win.name_prefix.clone();
let mut quote = main_win.quote.clone(); let mut quote = main_win.quote.clone();
let mut subquote = main_win.subquote.clone(); let mut subquote = main_win.subquote.clone();
let mut subquote2 = main_win.subquote2.clone(); let mut subquote2 = main_win.subquote2.clone();
@ -85,7 +90,7 @@ pub(crate) fn spawn_image_thread(
let mut status = main_win.status.clone(); let mut status = main_win.status.clone();
let mut count = main_win.count.clone(); let mut count = main_win.count.clone();
let mut dimension = main_win.dimension.clone(); let mut dimension = main_win.dimension.clone();
let images_path = Arc::clone(&main_win.images_path); let images_list = Arc::clone(&main_win.images_list);
let mut _container: Option<ImageContainer> = None; let mut _container: Option<ImageContainer> = None;
std::thread::spawn(move || loop { std::thread::spawn(move || loop {
@ -95,8 +100,9 @@ pub(crate) fn spawn_image_thread(
status.set_label("Loading..."); status.set_label("Loading...");
load_image( load_image(
&mut file_choice, &mut file_choice,
Arc::clone(&images_path), Arc::clone(&images_list),
None, None,
&mut name_prefix,
&mut quote, &mut quote,
&mut subquote, &mut subquote,
&mut subquote2, &mut subquote2,
@ -128,8 +134,9 @@ pub(crate) fn spawn_image_thread(
status.set_label("Loading..."); status.set_label("Loading...");
load_image( load_image(
&mut file_choice, &mut file_choice,
Arc::clone(&images_path), Arc::clone(&images_list),
Some((x, y)), Some((x, y)),
&mut name_prefix,
&mut quote, &mut quote,
&mut subquote, &mut subquote,
&mut subquote2, &mut subquote2,
@ -169,21 +176,23 @@ pub(crate) fn spawn_image_thread(
status.set_label("Saving..."); status.set_label("Saving...");
win.deactivate(); win.deactivate();
cont.save(); cont.save();
win.activate();
status.set_label(""); status.set_label("");
win.activate();
win.redraw();
app::awake();
} }
} }
DrawMessage::Clone => { DrawMessage::Clone => {
if let Some(cont) = &mut _container { if let Some(cont) = &mut _container {
status.set_label("Cloning..."); status.set_label("Cloning...");
win.deactivate(); win.deactivate();
if let Some(path) = cont.clone_img() { if let Some(image_info) = cont.clone_img() {
let idx = file_choice.value(); let idx = file_choice.value();
let mut imgs = images_path.write().unwrap(); let mut imgs = rw_write!(images_list);
imgs.insert(idx as usize, path.clone()); imgs.insert(idx as usize, image_info.clone());
file_choice.insert( file_choice.insert(
idx, idx,
path.file_name().unwrap().to_str().unwrap(), image_info.path.file_name().unwrap().to_str().unwrap(),
enums::Shortcut::None, enums::Shortcut::None,
menu::MenuFlag::Normal, menu::MenuFlag::Normal,
|a| a.do_callback(), |a| a.do_callback(),
@ -192,6 +201,8 @@ pub(crate) fn spawn_image_thread(
} }
status.set_label(""); status.set_label("");
win.activate(); win.activate();
win.redraw();
app::awake();
} }
} }
DrawMessage::Delete => { DrawMessage::Delete => {
@ -199,7 +210,7 @@ pub(crate) fn spawn_image_thread(
status.set_label("Deleting..."); status.set_label("Deleting...");
win.deactivate(); win.deactivate();
cont.delete(); cont.delete();
let mut imgs = images_path.write().unwrap(); let mut imgs = rw_write!(images_list);
imgs.remove(file_choice.value() as usize); imgs.remove(file_choice.value() as usize);
file_choice.remove(file_choice.value()); file_choice.remove(file_choice.value());
if file_choice.value() != imgs.len() as i32 { if file_choice.value() != imgs.len() as i32 {
@ -209,6 +220,17 @@ pub(crate) fn spawn_image_thread(
} }
status.set_label(""); status.set_label("");
win.activate(); win.activate();
win.redraw();
app::awake();
}
}
DrawMessage::ShowImagesDetails => show_images_details(Arc::clone(&images_list)),
DrawMessage::CheckImage => {
let (width, height) = rw_read!(properties).original_dimension;
if utils::is_too_small(width, height) {
if let Some(a) = &*rw_read!(globals::MAIN_SENDER) {
a.send(crate::AppMessage::DeleteImage);
}
} }
} }
} }
@ -219,8 +241,9 @@ pub(crate) fn spawn_image_thread(
/// Loads the selected image in file_choice to ImageContainer to edit /// Loads the selected image in file_choice to ImageContainer to edit
fn load_image( fn load_image(
file_choice: &mut menu::Choice, file_choice: &mut menu::Choice,
images_path: Arc<RwLock<Vec<PathBuf>>>, images_list: Arc<RwLock<Vec<ImageInfo>>>,
crop: Option<(f64, f64)>, crop: Option<(f64, f64)>,
name_prefix: &mut Input,
quote: &mut MultilineInput, quote: &mut MultilineInput,
subquote: &mut MultilineInput, subquote: &mut MultilineInput,
subquote2: &mut MultilineInput, subquote2: &mut MultilineInput,
@ -245,43 +268,36 @@ fn load_image(
properties: Arc<RwLock<ImageProperties>>, properties: Arc<RwLock<ImageProperties>>,
container: &mut Option<ImageContainer>, container: &mut Option<ImageContainer>,
) { ) {
let imgs = images_path.read().unwrap(); let imgs = rw_read!(images_list);
if imgs.len() == 0 { if imgs.len() == 0 {
*container = None; *container = None;
flush_buffer(app_sender, container); flush_buffer(app_sender, container);
return; return;
} }
count.set_label(&format!("[{}/{}]", file_choice.value() + 1, imgs.len())); count.set_label(&format!("[{}/{}]", file_choice.value() + 1, imgs.len()));
let file = imgs.get(file_choice.value() as usize).unwrap(); let image_info = imgs.get(file_choice.value() as usize).unwrap();
*container = Some(ImageContainer::new(&file, Arc::clone(&properties))); *container = Some(ImageContainer::new(&image_info, Arc::clone(&properties)));
if let Some(cont) = container { if let Some(cont) = container {
let file = Path::new(&file); let properties_file = utils::get_properties_path(&image_info);
let properties_file = file.with_extension("prop");
let read = fs::read_to_string(&properties_file).unwrap_or("{}".to_owned()); let read = fs::read_to_string(&properties_file).unwrap_or("{}".to_owned());
let read = match serde_json::from_str::<ImagePropertiesFile>(&read) { let read = match serde_json::from_str::<ImagePropertiesFile>(&read) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
warn!("Config is corrupt\n{:?}", e); Result::<(), _>::Err(e).warn_log("Config is corrupt");
match dialog::choice_default("Config is corrupt, fix??", "yes", "no", "") { fs::remove_file(&properties_file)
1 => { .warn_log("Failed to delete image properties file!");
if let Err(e) = fs::remove_file(&properties_file) {
dialog::alert_default("Failed to delete image properties file!");
warn!("Failed to delete image properties file!\n{:?}", e);
}
ImagePropertiesFile::default() ImagePropertiesFile::default()
} }
_ => return,
}
}
}; };
let mut properties = cont.properties.write().unwrap(); let mut properties = rw_write!(cont.properties);
properties.merge(read, &tag.value(), &tag2.value()); properties.merge(read, &tag.value(), &tag2.value());
properties.is_saved = true; properties.is_saved = true;
name_prefix.set_value(&properties.name_prefix);
quote.set_value(&properties.quote); quote.set_value(&properties.quote);
subquote.set_value(&properties.subquote); subquote.set_value(&properties.subquote);
subquote2.set_value(&properties.subquote2); subquote2.set_value(&properties.subquote2);
@ -332,7 +348,7 @@ fn load_image(
} }
cont.apply_resize(); cont.apply_resize();
let (width, height) = cont.properties.read().unwrap().dimension; let (width, height) = rw_read!(cont.properties).dimension;
page.col_flex.set_size(&page.image, height as i32); page.col_flex.set_size(&page.image, height as i32);
page.row_flex.set_size(&page.col_flex, width as i32); page.row_flex.set_size(&page.col_flex, width as i32);
page.col_flex.recalc(); page.col_flex.recalc();
@ -342,6 +358,40 @@ fn load_image(
flush_buffer(&app_sender, &container); flush_buffer(&app_sender, &container);
} }
fn show_images_details(images_list: Arc<RwLock<Vec<ImageInfo>>>) {
let mut image_with_quote: usize = 0;
let mut image_without_quote: usize = 0;
let list = rw_read!(images_list);
for image_info in list.iter() {
let properties_file = utils::get_properties_path(&image_info);
let read = fs::read_to_string(&properties_file).unwrap_or("{}".to_owned());
let read = match serde_json::from_str::<ImagePropertiesFile>(&read) {
Ok(r) => r,
Err(_) => {
image_without_quote += 1;
continue;
}
};
if let Some(t) = read.quote {
if t.trim().len() == 0 {
image_without_quote += 1;
} else {
image_with_quote += 1;
}
} else {
image_without_quote += 1;
}
}
utils::show_message(&format!(
"With Quote: {}\nWithout Quote: {}",
image_with_quote, image_without_quote
));
}
/// Flush the Buffer from image container to drawing buffer for fltk /// Flush the Buffer from image container to drawing buffer for fltk
// for drawing buffer for fltk (check in main.rs) // for drawing buffer for fltk (check in main.rs)
fn flush_buffer(app_sender: &app::Sender<crate::AppMessage>, container: &Option<ImageContainer>) { fn flush_buffer(app_sender: &app::Sender<crate::AppMessage>, container: &Option<ImageContainer>) {

268
src/export_all_window.rs Normal file
View File

@ -0,0 +1,268 @@
/*
This file is part of Post Maker.
Post Maker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Post Maker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Post Maker. If not, see <https://www.gnu.org/licenses/>
*/
//! Picker to pick config if multiple configs are present or defalut config is not present
use crate::{
config, dialog, globals,
result_ext::ResultExt,
utils::{self, ImageContainer, ImageInfo, ImageProperties, ImagePropertiesFile},
};
use bichannel::Channel;
use fltk::{
app::{self},
button::Button,
enums,
frame::Frame,
group::Flex,
image::SvgImage,
misc::Progress,
prelude::*,
window::Window,
};
use std::{
fs::File,
sync::{Arc, RwLock},
thread,
};
pub(crate) struct ExportAllWindow {
pub(crate) win: Window,
pub(crate) progress: Progress,
pub(crate) image_name: Frame,
pub(crate) close_btn: Button,
pub(crate) images_list: Arc<RwLock<Vec<ImageInfo>>>,
pub(crate) channel: Arc<RwLock<Option<Channel<ThreadMessage, ThreadMessage>>>>,
pub(crate) finished: Arc<RwLock<bool>>,
}
impl ExportAllWindow {
pub(crate) fn new(images_list: Arc<RwLock<Vec<ImageInfo>>>) -> Self {
let mut win = Window::new(0, 0, 500, 130, "Export All").center_screen();
win.set_icon(Some(
SvgImage::from_data(globals::ICON.to_str().unwrap()).unwrap(),
));
let progress_color = if *globals::THEME == config::Themes::Dark
|| *globals::THEME == config::Themes::HighContrast
{
enums::Color::rgb_color(0, 116, 80)
} else {
enums::Color::rgb_color(34, 203, 135)
};
let mut main_flex = Flex::default().size_of_parent().column();
//label
let mut panel_flex = Flex::default().row();
panel_flex.set_size(&Frame::default(), 1);
Frame::default()
.with_label("Exporting all with quotes")
.with_align(enums::Align::Left | enums::Align::Inside);
panel_flex.end();
main_flex.set_size(&panel_flex, 25);
//image name
let mut panel_flex = Flex::default().row();
panel_flex.set_size(&Frame::default(), 1);
let image_name = Frame::default()
.with_label("")
.with_align(enums::Align::Left | enums::Align::Inside);
panel_flex.end();
main_flex.set_size(&panel_flex, 25);
// progress bar
let mut panel_flex = Flex::default().row();
Frame::default();
let mut progress = Progress::default().with_label("Exporting...");
progress.set_maximum(1.0);
progress.set_value(0.0);
progress.set_frame(enums::FrameType::ThinDownBox);
progress.set_selection_color(progress_color);
Frame::default();
panel_flex.set_size(&progress, 490);
panel_flex.end();
main_flex.set_size(&panel_flex, 30);
//close button
let mut panel_flex = Flex::default().row();
Frame::default();
let close_btn = Button::default().with_label("Cancel");
panel_flex.set_size(&Frame::default(), 1);
panel_flex.set_size(&close_btn, 100);
panel_flex.end();
main_flex.set_size(&panel_flex, 30);
main_flex.end();
win.end();
win.make_modal(true);
let mut config_picker = Self {
win,
progress,
image_name,
close_btn,
images_list,
channel: Arc::new(RwLock::new(None)),
finished: Arc::new(RwLock::new(false)),
};
config_picker.event();
config_picker
}
pub(crate) fn export(&mut self) {
self.image_name.set_label("");
self.progress.set_label("Exporting...");
self.close_btn.set_label("Cancel");
self.progress.set_maximum(1.0);
self.progress.set_value(0.0);
*rw_write!(self.finished) = false;
self.win.show();
let (left, right) = bichannel::channel();
*rw_write!(self.channel) = Some(left);
spawn_export_thread(self, right);
while self.win.shown() {
if let Some(channel) = &*rw_read!(self.channel) {
if let Ok(msg) = channel.try_recv() {
match msg {
ThreadMessage::HideWindow => {
self.win.hide();
}
_ => (),
}
}
}
app::wait();
}
}
// Set callbacks of elements
fn event(&mut self) {
// Close Button
let channel = Arc::clone(&self.channel);
let finished = Arc::clone(&self.finished);
let mut win = self.win.clone();
self.close_btn.set_callback(move |_| {
if *rw_read!(finished) == true {
win.hide();
} else if dialog::choice_default("Are you sure?", "Yes", "No") == 0 {
if let Some(c) = &*rw_read!(channel) {
c.send(ThreadMessage::Stop).error_log("Failed to stop task");
}
}
});
// Window Close
let channel = Arc::clone(&self.channel);
let finished = Arc::clone(&self.finished);
self.win.set_callback(move |f| {
if *rw_read!(finished) == true {
f.hide();
} else if dialog::choice_default("Are you sure?", "Yes", "No") == 0 {
if let Some(c) = &*rw_read!(channel) {
c.send(ThreadMessage::Stop).error_log("Failed to stop task");
}
}
});
}
}
pub(crate) enum ThreadMessage {
Stop,
HideWindow,
}
fn spawn_export_thread(
export_all: &mut ExportAllWindow,
channel: Channel<ThreadMessage, ThreadMessage>,
) {
let mut win = export_all.win.clone();
let mut progress = export_all.progress.clone();
let mut image_name = export_all.image_name.clone();
let mut close_btn = export_all.close_btn.clone();
let finished = Arc::clone(&export_all.finished);
let images_list = Arc::clone(&export_all.images_list);
thread::spawn(move || {
let total = rw_read!(images_list).len();
progress.set_maximum(total as f64);
progress.set_value(0.0);
let mut update_progress = |idx: usize| {
progress.set_value(idx as f64 + 1.0);
progress.set_label(&format!("[{}/{}]", idx + 1, total));
};
for (idx, image) in (*rw_read!(images_list)).iter().enumerate() {
image_name.set_label(
image
.path
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default(),
);
let properties = Arc::new(RwLock::new(ImageProperties::default()));
let container = ImageContainer::new(image, properties);
let properties_file = utils::get_properties_path(image);
let read = match File::open(&properties_file) {
Ok(r) => r,
Err(_) => {
update_progress(idx);
continue;
}
};
let read = match serde_json::from_reader::<File, ImagePropertiesFile>(read) {
Ok(r) => r,
Err(_) => {
update_progress(idx);
continue;
}
};
rw_write!(container.properties).merge(read, "", "");
if rw_read!(container.properties).quote.trim().len() == 0 {
update_progress(idx);
continue;
}
container.save();
update_progress(idx);
win.redraw();
app::awake();
if let Ok(msg) = channel.try_recv() {
match msg {
ThreadMessage::Stop => {
channel
.send(ThreadMessage::HideWindow)
.error_log("Failed to close window");
return;
}
_ => (),
}
}
}
image_name.set_label("Finished");
progress.set_value(total as f64);
close_btn.set_label("Close");
*rw_write!(finished) = true;
win.redraw();
app::awake();
});
}

View File

@ -12,7 +12,7 @@
along with Post Maker. If not, see <https://www.gnu.org/licenses/> along with Post Maker. If not, see <https://www.gnu.org/licenses/>
*/ */
use crate::config; use crate::{config, result_ext::ResultExt};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rusttype::Font; use rusttype::Font;
use std::{ffi::OsString, io::Read, sync::RwLock}; use std::{ffi::OsString, io::Read, sync::RwLock};
@ -29,86 +29,23 @@ lazy_static! {
pub(crate) static ref CONFIG: RwLock<config::ConfigFile> = pub(crate) static ref CONFIG: RwLock<config::ConfigFile> =
RwLock::new(config::ConfigFile::load()); RwLock::new(config::ConfigFile::load());
/// Main Sender
pub(crate) static ref MAIN_SENDER: RwLock<Option<fltk::app::Sender<crate::AppMessage>>> = RwLock::new(None);
/// TTF Font for Quote /// TTF Font for Quote
pub(crate) static ref FONT_QUOTE: Font<'static> = { pub(crate) static ref FONT_QUOTE: Font<'static> = load_font(rw_read!(CONFIG).quote_font.as_str());
let mut buffer = Vec::new();
if let Ok(mut file) = std::fs::File::open(CONFIG.read().unwrap().quote_font.as_str()) {
if let Ok(_) = file.read_to_end(&mut buffer) {
if let Some(out) = rusttype::Font::try_from_vec(buffer) {
return out;
}
}
}
rusttype::Font::try_from_vec(
include_bytes!("../assets/fonts/ReenieBeanie-Regular.ttf").to_vec(),
)
.unwrap()
};
/// TTF Font for Subquote /// TTF Font for Subquote
pub(crate) static ref FONT_SUBQUOTE: Font<'static> = { pub(crate) static ref FONT_SUBQUOTE: Font<'static> = load_font(rw_read!(CONFIG).subquote_font.as_str());
let mut buffer = Vec::new();
if let Ok(mut file) = std::fs::File::open(CONFIG.read().unwrap().subquote_font.as_str())
{
if let Ok(_) = file.read_to_end(&mut buffer) {
if let Some(out) = rusttype::Font::try_from_vec(buffer) {
return out;
}
}
}
rusttype::Font::try_from_vec(
include_bytes!("../assets/fonts/ReenieBeanie-Regular.ttf").to_vec(),
)
.unwrap()
};
/// TTF Font for Subquote 2 /// TTF Font for Subquote 2
pub(crate) static ref FONT_SUBQUOTE2: Font<'static> = { pub(crate) static ref FONT_SUBQUOTE2: Font<'static> = load_font(rw_read!(CONFIG).subquote2_font.as_str());
let mut buffer = Vec::new();
if let Ok(mut file) =
std::fs::File::open(CONFIG.read().unwrap().subquote2_font.as_str())
{
if let Ok(_) = file.read_to_end(&mut buffer) {
if let Some(out) = rusttype::Font::try_from_vec(buffer) {
return out;
}
}
}
rusttype::Font::try_from_vec(
include_bytes!("../assets/fonts/Rajdhani-Regular.ttf").to_vec(),
)
.unwrap()
};
/// TTF Font for Tag /// TTF Font for Tag
pub(crate) static ref FONT_TAG: Font<'static> = { pub(crate) static ref FONT_TAG: Font<'static> = load_font(rw_read!(CONFIG).tag_font.as_str());
let mut buffer = Vec::new();
if let Ok(mut file) = std::fs::File::open(&CONFIG.read().unwrap().tag_font.as_str()) {
if let Ok(_) = file.read_to_end(&mut buffer) {
if let Some(out) = rusttype::Font::try_from_vec(buffer) {
return out;
}
}
}
rusttype::Font::try_from_vec(include_bytes!("../assets/fonts/Kalam-Regular.ttf").to_vec())
.unwrap()
};
/// TTF Font for Tag 2 /// TTF Font for Tag 2
pub(crate) static ref FONT_TAG2: Font<'static> = { pub(crate) static ref FONT_TAG2: Font<'static> = load_font(rw_read!(CONFIG).tag2_font.as_str());
let mut buffer = Vec::new();
if let Ok(mut file) = std::fs::File::open(&CONFIG.read().unwrap().tag2_font.as_str()) {
if let Ok(_) = file.read_to_end(&mut buffer) {
if let Some(out) = rusttype::Font::try_from_vec(buffer) {
return out;
}
}
}
rusttype::Font::try_from_vec(
include_bytes!("../assets/fonts/Rajdhani-Regular.ttf").to_vec(),
)
.unwrap()
};
/// Image to use for Window /// Image to use for Window
pub(crate) static ref ICON: OsString = include_str!("../assets/icon.svg").into(); pub(crate) static ref ICON: OsString = include_str!("../assets/icon.svg").into();
@ -125,3 +62,15 @@ lazy_static! {
img.into() img.into()
}; };
} }
fn load_font(path: &str) -> Font<'static> {
let mut buffer = Vec::new();
if let Ok(mut file) = std::fs::File::open(path) {
if let Ok(_) = file.read_to_end(&mut buffer) {
if let Some(out) = rusttype::Font::try_from_vec(buffer) {
return out;
}
}
}
rusttype::Font::try_from_vec(include_bytes!("../assets/OpenSans-Regular.ttf").to_vec()).unwrap()
}

27
src/macros.rs Normal file
View File

@ -0,0 +1,27 @@
/*
This file is part of Post Maker.
Post Maker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Post Maker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Post Maker. If not, see <https://www.gnu.org/licenses/>
*/
#[macro_export]
macro_rules! rw_read {
($var:expr) => {
$var.read().expect_log("Program got panic!")
};
}
#[macro_export]
macro_rules! rw_write {
($var:expr) => {
$var.write().expect_log("Program got panic!")
};
}

View File

@ -17,23 +17,29 @@
extern crate log; extern crate log;
extern crate simplelog; extern crate simplelog;
mod about; #[macro_use]
mod macros;
mod about_window;
mod config; mod config;
mod config_picker; mod config_picker;
mod config_window; mod config_window;
mod crop_window; mod crop_window;
mod dialog;
mod draw_thread; mod draw_thread;
mod export_all_window;
mod globals; mod globals;
mod main_window; mod main_window;
mod result_ext;
mod utils; mod utils;
use fltk::{ use fltk::{
app::{channel, App}, app::{channel, App},
dialog,
prelude::*, prelude::*,
}; };
use fltk_theme::WidgetTheme; use fltk_theme::WidgetTheme;
use main_window::MainWindow; use main_window::MainWindow;
use result_ext::ResultExt;
use simplelog::*; use simplelog::*;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
@ -41,17 +47,27 @@ use std::sync::{Arc, RwLock};
pub(crate) enum AppMessage { pub(crate) enum AppMessage {
/// Copy recived image buffer from draw_thread to Buffer for fltk frame /// Copy recived image buffer from draw_thread to Buffer for fltk frame
RedrawMainWindowImage(Option<Vec<u8>>), RedrawMainWindowImage(Option<Vec<u8>>),
Message(String),
Alert(String),
ProgramPanicMessage(String),
// Only for Main windows
DeleteImage,
} }
fn main() { fn main() {
let app = App::default(); let app = App::default();
WidgetTheme::new(globals::THEME.clone().into()).apply(); WidgetTheme::new(globals::THEME.clone().into()).apply();
if let Err(e) = CombinedLogger::init(vec![WriteLogger::new( if let Err(e) = CombinedLogger::init(vec![
WriteLogger::new(LevelFilter::Warn, Config::default(), config::log_file()),
TermLogger::new(
LevelFilter::Info, LevelFilter::Info,
Config::default(), Config::default(),
config::log_file(), TerminalMode::Mixed,
)]) { ColorChoice::Auto,
),
]) {
dialog::alert_default("Failed to start logger"); dialog::alert_default("Failed to start logger");
panic!("Failed to start logger\n{:?}", e); panic!("Failed to start logger\n{:?}", e);
} }
@ -62,16 +78,41 @@ fn main() {
let draw_buff: Arc<RwLock<Option<Vec<u8>>>> = Arc::new(RwLock::new(None)); let draw_buff: Arc<RwLock<Option<Vec<u8>>>> = Arc::new(RwLock::new(None));
let (main_sender, main_receiver) = channel::<AppMessage>(); let (main_sender, main_receiver) = channel::<AppMessage>();
let mut main_window = MainWindow::new(main_sender, Arc::clone(&draw_buff)); *rw_write!(globals::MAIN_SENDER) = Some(main_sender);
let mut main_window = MainWindow::new(Arc::clone(&draw_buff));
while app.wait() { while app.wait() {
if let Some(msg) = main_receiver.recv() { if let Some(msg) = main_receiver.recv() {
match msg { match msg {
AppMessage::RedrawMainWindowImage(data) => { AppMessage::RedrawMainWindowImage(data) => {
let mut buff = draw_buff.write().unwrap(); let mut buff = rw_write!(draw_buff);
*buff = data; *buff = data;
main_window.win.redraw(); main_window.win.redraw();
} }
AppMessage::Message(msg) => {
dialog::message_default(&msg);
}
AppMessage::Alert(msg) => dialog::alert_default(&msg),
AppMessage::ProgramPanicMessage(msg) => {
dialog::message_default(&msg);
std::process::exit(1);
}
// Fltk does not show dialogs from other thread in windows, So this is hack to get things done
AppMessage::DeleteImage => {
let ch = dialog::choice_default("Image is too small", "Delete", "Keep");
if ch == 0 {
main_window
.sender
.send(draw_thread::DrawMessage::Delete)
.unwrap();
main_window
.sender
.send(draw_thread::DrawMessage::Open)
.unwrap();
main_window.page.image.redraw();
main_window.file_choice.redraw();
}
}
} }
} }
} }

View File

@ -13,16 +13,19 @@
*/ */
//! Main window where you do all editing //! Main window where you do all editing
use crate::about::About; use crate::{
use crate::crop_window::CropWindow; about_window::About,
use crate::draw_thread::*; config_window::ConfigWindow,
use crate::utils; crop_window::CropWindow,
use crate::utils::ImageProperties;
use crate::{config_window::ConfigWindow, globals};
use fltk::{
app,
button::Button,
dialog, dialog,
draw_thread::*,
export_all_window::ExportAllWindow,
globals,
result_ext::ResultExt,
utils::{self, ImageInfo, ImageProperties, ImageType},
};
use fltk::{
button::Button,
dialog::NativeFileChooser, dialog::NativeFileChooser,
draw as dr, enums, draw as dr, enums,
enums::Shortcut, enums::Shortcut,
@ -36,9 +39,14 @@ use fltk::{
valuator::{Slider, SliderType}, valuator::{Slider, SliderType},
window::Window, window::Window,
}; };
use std::path::PathBuf; use std::{
use std::sync::{mpsc, RwLock}; ffi::OsStr,
use std::{ffi::OsStr, fs, sync::Arc}; fs,
path::PathBuf,
process::Command,
sync::Arc,
sync::{mpsc, RwLock},
};
pub(crate) struct MainWindow { pub(crate) struct MainWindow {
pub(crate) win: Window, pub(crate) win: Window,
@ -48,6 +56,7 @@ pub(crate) struct MainWindow {
pub(crate) save_btn: Button, pub(crate) save_btn: Button,
/// To choose the file which is being edited in directory /// To choose the file which is being edited in directory
pub(crate) file_choice: menu::Choice, pub(crate) file_choice: menu::Choice,
pub(crate) name_prefix: Input,
pub(crate) quote: MultilineInput, pub(crate) quote: MultilineInput,
pub(crate) subquote: MultilineInput, pub(crate) subquote: MultilineInput,
pub(crate) subquote2: MultilineInput, pub(crate) subquote2: MultilineInput,
@ -81,7 +90,7 @@ pub(crate) struct MainWindow {
pub(crate) count: Frame, pub(crate) count: Frame,
pub(crate) dimension: Frame, pub(crate) dimension: Frame,
pub(crate) page: Page, pub(crate) page: Page,
pub(crate) images_path: Arc<RwLock<Vec<PathBuf>>>, pub(crate) images_list: Arc<RwLock<Vec<ImageInfo>>>,
pub(crate) draw_buff: Arc<RwLock<Option<Vec<u8>>>>, pub(crate) draw_buff: Arc<RwLock<Option<Vec<u8>>>>,
pub(crate) properties: Arc<RwLock<ImageProperties>>, pub(crate) properties: Arc<RwLock<ImageProperties>>,
pub(crate) sender: mpsc::Sender<DrawMessage>, pub(crate) sender: mpsc::Sender<DrawMessage>,
@ -96,10 +105,7 @@ pub(crate) struct Page {
} }
impl MainWindow { impl MainWindow {
pub(crate) fn new( pub(crate) fn new(draw_buff: Arc<RwLock<Option<Vec<u8>>>>) -> Self {
sender: app::Sender<crate::AppMessage>,
draw_buff: Arc<RwLock<Option<Vec<u8>>>>,
) -> Self {
let mut win = Window::new(0, 0, 1100, 700, "Post Maker").center_screen(); let mut win = Window::new(0, 0, 1100, 700, "Post Maker").center_screen();
win.set_icon(Some( win.set_icon(Some(
SvgImage::from_data(globals::ICON.to_str().unwrap()).unwrap(), SvgImage::from_data(globals::ICON.to_str().unwrap()).unwrap(),
@ -128,6 +134,15 @@ impl MainWindow {
let mut workspace_flex = Flex::default().row(); let mut workspace_flex = Flex::default().row();
// Controls Left // Controls Left
let mut left_controls_flex = Flex::default().column(); let mut left_controls_flex = Flex::default().column();
left_controls_flex.set_size(
&Frame::default()
.with_label("Name Prefix:")
.with_align(enums::Align::Left | enums::Align::Inside),
25,
);
let name_prefix = Input::default();
left_controls_flex.set_size(&name_prefix, 30);
left_controls_flex.set_size( left_controls_flex.set_size(
&Frame::default() &Frame::default()
.with_label("Quote:") .with_label("Quote:")
@ -350,6 +365,7 @@ impl MainWindow {
next_btn, next_btn,
save_btn, save_btn,
file_choice, file_choice,
name_prefix,
quote, quote,
subquote, subquote,
subquote2, subquote2,
@ -380,7 +396,7 @@ impl MainWindow {
status, status,
count, count,
dimension, dimension,
images_path: Arc::new(RwLock::new(vec![])), images_list: Arc::new(RwLock::new(vec![])),
draw_buff, draw_buff,
properties: Arc::clone(&properties), properties: Arc::clone(&properties),
page: Page { page: Page {
@ -390,7 +406,10 @@ impl MainWindow {
}, },
sender: rx, sender: rx,
}; };
spawn_image_thread(tx, sender, Arc::clone(&properties), &main_win);
if let Some(a) = &*rw_read!(globals::MAIN_SENDER) {
spawn_image_thread(tx, a.to_owned(), Arc::clone(&properties), &main_win);
}
main_win.menu(); main_win.menu();
main_win.draw(); main_win.draw();
main_win.events(); main_win.events();
@ -401,7 +420,8 @@ impl MainWindow {
fn menu(&mut self) { fn menu(&mut self) {
let mut file_choice = self.file_choice.clone(); let mut file_choice = self.file_choice.clone();
let sender = self.sender.clone(); let sender = self.sender.clone();
let imgs = Arc::clone(&self.images_path); let imgs = Arc::clone(&self.images_list);
let mut win = self.win.clone();
self.menubar.add( self.menubar.add(
"&File/Open Folder...\t", "&File/Open Folder...\t",
Shortcut::Ctrl | 'o', Shortcut::Ctrl | 'o',
@ -415,14 +435,12 @@ impl MainWindow {
if !path.exists() { if !path.exists() {
return; return;
} }
let expost_dir = path.join("export"); win.set_label(&format!(
if !expost_dir.exists() { "{} - Post Maker",
if let Err(e) = fs::create_dir(expost_dir) { path.file_name()
fltk::dialog::alert_default("Failed to create export folder!"); .unwrap_or(OsStr::new("Unknown"))
warn!("Failed to create export folder!\n{:?}", e); .to_string_lossy()
return; ));
}
}
load_dir(&path, Arc::clone(&imgs), &mut file_choice, &sender); load_dir(&path, Arc::clone(&imgs), &mut file_choice, &sender);
}, },
); );
@ -430,13 +448,93 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.menubar.add( self.menubar.add(
"&File/Save...\t", "&File/Save Image...\t",
Shortcut::Ctrl | 's', Shortcut::Ctrl | 's',
menu::MenuFlag::Normal, menu::MenuFlag::Normal,
move |_| { move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.is_saved = true; prop.is_saved = true;
sender.send(DrawMessage::Save).unwrap(); sender.send_it(DrawMessage::Save);
},
);
let sender = self.sender.clone();
self.menubar.add(
"&Actions/Show Details...\t",
Shortcut::None,
menu::MenuFlag::Normal,
move |_| {
sender.send_it(DrawMessage::ShowImagesDetails);
},
);
let properties = Arc::clone(&self.properties);
self.menubar.add(
"&Actions/Open Exports Folder...\t",
Shortcut::None,
menu::MenuFlag::Normal,
move |_| {
let props = rw_read!(properties);
if let Some(prop) = &props.image_info {
let export = prop.path.parent().unwrap().join("export");
if export.exists() {
if cfg!(windows) {
Command::new("explorer")
.arg(export.to_str().unwrap_or_default())
.spawn()
.warn_log("Failed top spawn command");
} else if cfg!(unix) {
Command::new("xdg-open")
.arg(export.to_str().unwrap_or_default())
.spawn()
.warn_log("Failed top spawn command");
} else if cfg!(macos) {
Command::new("open")
.arg(export.to_str().unwrap_or_default())
.spawn()
.warn_log("Failed top spawn command");
} else {
dialog::alert_default("Unknown Operating System")
}
}
}
},
);
let mut win = self.win.clone();
let mut export_all = ExportAllWindow::new(Arc::clone(&self.images_list));
self.menubar.add(
"&Actions/Export All with Quotes...\t",
Shortcut::None,
menu::MenuFlag::Normal,
move |_| {
export_all.export();
win.redraw();
fltk::app::awake();
},
);
let properties = Arc::clone(&self.properties);
let mut win = self.win.clone();
self.menubar.add(
"&Actions/Delete Exports...\t",
Shortcut::None,
menu::MenuFlag::Normal,
move |_| {
win.deactivate();
if dialog::choice_default("Do you want to remove exports?", "Yes", "No") == 0 {
let props = rw_read!(properties);
if let Some(prop) = &props.image_info {
let export = prop.path.parent().unwrap().join("export");
if export.exists() {
fs::remove_dir_all(&export)
.warn_log("Failed to remove export directory");
}
}
}
win.activate();
win.redraw();
fltk::app::awake();
}, },
); );
@ -449,8 +547,8 @@ impl MainWindow {
menu::MenuFlag::Normal, menu::MenuFlag::Normal,
move |_| { move |_| {
if config_window.show() { if config_window.show() {
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
} }
}, },
@ -472,8 +570,8 @@ impl MainWindow {
let buff = Arc::clone(&self.draw_buff); let buff = Arc::clone(&self.draw_buff);
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.page.image.draw(move |f| { self.page.image.draw(move |f| {
let (width, height) = properties.read().unwrap().dimension; let (width, height) = rw_read!(properties).dimension;
if let Some(image) = &*buff.read().unwrap() { if let Some(image) = &*rw_read!(buff) {
dr::draw_image( dr::draw_image(
&image, &image,
f.x(), f.x(),
@ -492,10 +590,10 @@ impl MainWindow {
// Resest Button for FileChoice // Resest Button for FileChoice
let mut file_choice = self.file_choice.clone(); let mut file_choice = self.file_choice.clone();
let sender = self.sender.clone(); let sender = self.sender.clone();
let imgs = Arc::clone(&self.images_path); let imgs = Arc::clone(&self.images_list);
self.reset_file_choice.set_callback(move |_| { self.reset_file_choice.set_callback(move |_| {
let path = match imgs.read().unwrap().first() { let path = match rw_read!(imgs).first() {
Some(path) => path.parent().unwrap().to_path_buf(), Some(image_info) => image_info.path.parent().unwrap().to_path_buf(),
None => return, None => return,
}; };
load_dir(&path, Arc::clone(&imgs), &mut file_choice, &sender); load_dir(&path, Arc::clone(&imgs), &mut file_choice, &sender);
@ -508,14 +606,14 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.reset_translucent_layer_btn.set_callback(move |_| { self.reset_translucent_layer_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let color = globals::CONFIG.read().unwrap().color_layer; let color = rw_read!(globals::CONFIG).color_layer;
prop.translucent_layer_color = color; prop.translucent_layer_color = color;
prop.is_saved = false; prop.is_saved = false;
utils::set_color_btn_rgba(color, &mut layer_rgb); utils::set_color_btn_rgba(color, &mut layer_rgb);
layer_alpha.set_value(color[3] as f64); layer_alpha.set_value(color[3] as f64);
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -526,16 +624,16 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.reset_quote_position_btn.set_callback(move |_| { self.reset_quote_position_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let height = prop.original_dimension.1; let height = prop.original_dimension.1;
let pos = height * globals::CONFIG.read().unwrap().quote_position_ratio; let pos = height * rw_read!(globals::CONFIG).quote_position_ratio;
prop.quote_position = pos; prop.quote_position = pos;
prop.is_saved = false; prop.is_saved = false;
quote_position.set_value(pos); quote_position.set_value(pos);
quote_position_slider.set_value(pos); quote_position_slider.set_value(pos);
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -546,16 +644,16 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.reset_subquote_position_btn.set_callback(move |_| { self.reset_subquote_position_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let height = prop.original_dimension.1; let height = prop.original_dimension.1;
let pos = height * globals::CONFIG.read().unwrap().subquote_position_ratio; let pos = height * rw_read!(globals::CONFIG).subquote_position_ratio;
prop.subquote_position = pos; prop.subquote_position = pos;
prop.is_saved = false; prop.is_saved = false;
subquote_position.set_value(pos); subquote_position.set_value(pos);
subquote_position_slider.set_value(pos); subquote_position_slider.set_value(pos);
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -566,16 +664,16 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.reset_subquote2_position_btn.set_callback(move |_| { self.reset_subquote2_position_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let height = prop.original_dimension.1; let height = prop.original_dimension.1;
let pos = height * globals::CONFIG.read().unwrap().subquote2_position_ratio; let pos = height * rw_read!(globals::CONFIG).subquote2_position_ratio;
prop.subquote2_position = pos; prop.subquote2_position = pos;
prop.is_saved = false; prop.is_saved = false;
subquote2_position.set_value(pos); subquote2_position.set_value(pos);
subquote2_position_slider.set_value(pos); subquote2_position_slider.set_value(pos);
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -586,16 +684,16 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.reset_tag_position_btn.set_callback(move |_| { self.reset_tag_position_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let height = prop.original_dimension.1; let height = prop.original_dimension.1;
let pos = height * globals::CONFIG.read().unwrap().tag_position_ratio; let pos = height * rw_read!(globals::CONFIG).tag_y_position_ratio;
prop.tag_position = pos; prop.tag_position = pos;
prop.is_saved = false; prop.is_saved = false;
tag_position.set_value(pos); tag_position.set_value(pos);
tag_position_slider.set_value(pos); tag_position_slider.set_value(pos);
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -606,16 +704,16 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.reset_tag2_position_btn.set_callback(move |_| { self.reset_tag2_position_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let height = prop.original_dimension.1; let height = prop.original_dimension.1;
let pos = height * globals::CONFIG.read().unwrap().tag2_position_ratio; let pos = height * rw_read!(globals::CONFIG).tag2_position_ratio;
prop.tag2_position = pos; prop.tag2_position = pos;
prop.is_saved = false; prop.is_saved = false;
tag2_position.set_value(pos); tag2_position.set_value(pos);
tag2_position_slider.set_value(pos); tag2_position_slider.set_value(pos);
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -623,9 +721,9 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.save_btn.set_callback(move |_| { self.save_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.is_saved = true; prop.is_saved = true;
sender.send(DrawMessage::Save).unwrap() sender.send_it(DrawMessage::Save);
}); });
// Clone Button // Clone Button
@ -633,10 +731,11 @@ impl MainWindow {
let mut file_choice = self.file_choice.clone(); let mut file_choice = self.file_choice.clone();
let sender = self.sender.clone(); let sender = self.sender.clone();
self.clone_btn.set_callback(move |_| { self.clone_btn.set_callback(move |_| {
let ch = dialog::choice_default("Do you want to clone??", "Yes", "No", ""); let ch = dialog::choice_default("Do you want to clone??", "Yes", "No");
if ch == 0 { if ch == 0 {
sender.send(DrawMessage::Clone).unwrap(); sender.send_it(DrawMessage::Clone);
sender.send(DrawMessage::Open).unwrap(); sender.send_it(DrawMessage::Open);
sender.send_it(DrawMessage::CheckImage);
image.redraw(); image.redraw();
file_choice.redraw(); file_choice.redraw();
} }
@ -647,10 +746,11 @@ impl MainWindow {
let mut file_choice = self.file_choice.clone(); let mut file_choice = self.file_choice.clone();
let sender = self.sender.clone(); let sender = self.sender.clone();
self.delete_btn.set_callback(move |_| { self.delete_btn.set_callback(move |_| {
let ch = dialog::choice_default("Do you want to delete??", "Yes", "No", ""); let ch = dialog::choice_default("Do you want to delete??", "Yes", "No");
if ch == 0 { if ch == 0 {
sender.send(DrawMessage::Delete).unwrap(); sender.send_it(DrawMessage::Delete);
sender.send(DrawMessage::Open).unwrap(); sender.send_it(DrawMessage::Open);
sender.send_it(DrawMessage::CheckImage);
image.redraw(); image.redraw();
file_choice.redraw(); file_choice.redraw();
} }
@ -661,10 +761,10 @@ impl MainWindow {
let mut crop_win = CropWindow::new(); let mut crop_win = CropWindow::new();
let sender = self.sender.clone(); let sender = self.sender.clone();
self.crop_btn.set_callback(move |_| { self.crop_btn.set_callback(move |_| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
if let Some(path) = &prop.path { if let Some(image_info) = &prop.image_info {
if let Some((x, y)) = crop_win.load_to_crop(path, prop.crop_position) { if let Some((x, y)) = crop_win.load_to_crop(&image_info, prop.crop_position) {
sender.send(DrawMessage::ChangeCrop((x, y))).unwrap(); sender.send_it(DrawMessage::ChangeCrop((x, y)));
prop.is_saved = false; prop.is_saved = false;
} }
} }
@ -675,11 +775,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.next_btn.set_callback(move |_| { self.next_btn.set_callback(move |_| {
let prop = properties.read().unwrap(); let prop = rw_read!(properties);
if !prop.is_saved { if !prop.is_saved {
let save = fltk::dialog::choice_default("Save?", "yes", "no", "cancel"); let save = fltk::dialog::choice2_default("Save?", "yes", "no", "cancel");
match save { match save.unwrap_or(-1) {
0 => sender.send(DrawMessage::Save).unwrap(), 0 => sender.send_it(DrawMessage::Save),
1 => {} 1 => {}
_ => return, _ => return,
} }
@ -690,7 +790,8 @@ impl MainWindow {
} else { } else {
file_choice.set_value(file_choice.value() + 1); file_choice.set_value(file_choice.value() + 1);
} }
sender.send(DrawMessage::Open).unwrap(); sender.send_it(DrawMessage::Open);
sender.send_it(DrawMessage::CheckImage);
}); });
// Back Image Button // Back Image Button
@ -698,11 +799,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.back_btn.set_callback(move |_| { self.back_btn.set_callback(move |_| {
let prop = properties.read().unwrap(); let prop = rw_read!(properties);
if !prop.is_saved { if !prop.is_saved {
let save = fltk::dialog::choice_default("Save?", "yes", "no", "cancel"); let save = fltk::dialog::choice2_default("Save?", "yes", "no", "cancel");
match save { match save.unwrap_or(-1) {
0 => sender.send(DrawMessage::Save).unwrap(), 0 => sender.send_it(DrawMessage::Save),
1 => {} 1 => {}
_ => return, _ => return,
} }
@ -713,23 +814,36 @@ impl MainWindow {
} else { } else {
file_choice.set_value(file_choice.value() - 1); file_choice.set_value(file_choice.value() - 1);
} }
sender.send(DrawMessage::Open).unwrap(); sender.send_it(DrawMessage::Open);
sender.send_it(DrawMessage::CheckImage);
}); });
// File Choice // File Choice
let sender = self.sender.clone(); let sender = self.sender.clone();
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
self.file_choice.set_callback(move |_| { self.file_choice.set_callback(move |_| {
let prop = properties.read().unwrap(); let prop = rw_read!(properties);
if !prop.is_saved { if !prop.is_saved {
let save = fltk::dialog::choice_default("Save?", "yes", "no", "cancel"); let save = fltk::dialog::choice2_default("Save?", "yes", "no", "cancel");
match save { match save.unwrap_or(-1) {
0 => sender.send(DrawMessage::Save).unwrap(), 0 => sender.send_it(DrawMessage::Save),
1 => {} 1 => {}
_ => return, _ => return,
} }
} }
sender.send(DrawMessage::Open).unwrap(); sender.send_it(DrawMessage::Open);
sender.send_it(DrawMessage::CheckImage);
});
// Name Prefix Input
let properties = Arc::clone(&self.properties);
self.name_prefix.handle(move |f, ev| {
if ev == enums::Event::KeyUp {
let mut prop = rw_write!(properties);
prop.name_prefix = f.value();
prop.is_saved = false;
}
true
}); });
// Quote Input // Quote Input
@ -738,11 +852,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
self.quote.handle(move |f, ev| { self.quote.handle(move |f, ev| {
if ev == enums::Event::KeyUp { if ev == enums::Event::KeyUp {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.quote = f.value(); prop.quote = f.value();
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
} }
true true
@ -754,11 +868,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
self.subquote.handle(move |f, ev| { self.subquote.handle(move |f, ev| {
if ev == enums::Event::KeyUp { if ev == enums::Event::KeyUp {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.subquote = f.value(); prop.subquote = f.value();
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
} }
true true
@ -770,11 +884,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
self.subquote2.handle(move |f, ev| { self.subquote2.handle(move |f, ev| {
if ev == enums::Event::KeyUp { if ev == enums::Event::KeyUp {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.subquote2 = f.value(); prop.subquote2 = f.value();
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
} }
true true
@ -786,11 +900,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
self.tag.handle(move |f, ev| { self.tag.handle(move |f, ev| {
if ev == enums::Event::KeyUp { if ev == enums::Event::KeyUp {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.tag = f.value(); prop.tag = f.value();
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
} }
true true
@ -802,11 +916,11 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
self.tag2.handle(move |f, ev| { self.tag2.handle(move |f, ev| {
if ev == enums::Event::KeyUp { if ev == enums::Event::KeyUp {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.tag2 = f.value(); prop.tag2 = f.value();
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
} }
true true
@ -818,12 +932,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut quote_position_slider = self.quote_position_slider.clone(); let mut quote_position_slider = self.quote_position_slider.clone();
self.quote_position.set_callback(move |f| { self.quote_position.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.quote_position = f.value(); prop.quote_position = f.value();
quote_position_slider.set_value(f.value()); quote_position_slider.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -833,12 +947,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut quote_position = self.quote_position.clone(); let mut quote_position = self.quote_position.clone();
self.quote_position_slider.set_callback(move |f| { self.quote_position_slider.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.quote_position = f.value(); prop.quote_position = f.value();
quote_position.set_value(f.value()); quote_position.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -848,12 +962,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut subquote_position_slider = self.subquote_position_slider.clone(); let mut subquote_position_slider = self.subquote_position_slider.clone();
self.subquote_position.set_callback(move |f| { self.subquote_position.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.subquote_position = f.value(); prop.subquote_position = f.value();
subquote_position_slider.set_value(f.value()); subquote_position_slider.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -863,12 +977,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut subquote_position = self.subquote_position.clone(); let mut subquote_position = self.subquote_position.clone();
self.subquote_position_slider.set_callback(move |f| { self.subquote_position_slider.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.subquote_position = f.value(); prop.subquote_position = f.value();
subquote_position.set_value(f.value()); subquote_position.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -878,12 +992,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut subquote2_position_slider = self.subquote2_position_slider.clone(); let mut subquote2_position_slider = self.subquote2_position_slider.clone();
self.subquote2_position.set_callback(move |f| { self.subquote2_position.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.subquote2_position = f.value(); prop.subquote2_position = f.value();
subquote2_position_slider.set_value(f.value()); subquote2_position_slider.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -893,12 +1007,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut subquote2_position = self.subquote2_position.clone(); let mut subquote2_position = self.subquote2_position.clone();
self.subquote2_position_slider.set_callback(move |f| { self.subquote2_position_slider.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.subquote2_position = f.value(); prop.subquote2_position = f.value();
subquote2_position.set_value(f.value()); subquote2_position.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -908,12 +1022,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut tag_position_slider = self.tag_position_slider.clone(); let mut tag_position_slider = self.tag_position_slider.clone();
self.tag_position.set_callback(move |f| { self.tag_position.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.tag_position = f.value(); prop.tag_position = f.value();
tag_position_slider.set_value(f.value()); tag_position_slider.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -923,12 +1037,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut tag_position = self.tag_position.clone(); let mut tag_position = self.tag_position.clone();
self.tag_position_slider.set_callback(move |f| { self.tag_position_slider.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.tag_position = f.value(); prop.tag_position = f.value();
tag_position.set_value(f.value()); tag_position.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -938,12 +1052,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut tag2_position_slider = self.tag2_position_slider.clone(); let mut tag2_position_slider = self.tag2_position_slider.clone();
self.tag2_position.set_callback(move |f| { self.tag2_position.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.tag2_position = f.value(); prop.tag2_position = f.value();
tag2_position_slider.set_value(f.value()); tag2_position_slider.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -953,12 +1067,12 @@ impl MainWindow {
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut tag2_position = self.tag2_position.clone(); let mut tag2_position = self.tag2_position.clone();
self.tag2_position_slider.set_callback(move |f| { self.tag2_position_slider.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.tag2_position = f.value(); prop.tag2_position = f.value();
tag2_position.set_value(f.value()); tag2_position.set_value(f.value());
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -967,7 +1081,7 @@ impl MainWindow {
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
let sender = self.sender.clone(); let sender = self.sender.clone();
self.translucent_layer_rgb.set_callback(move |mut f| { self.translucent_layer_rgb.set_callback(move |mut f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
let (r, g, b) = dialog::color_chooser_with_default( let (r, g, b) = dialog::color_chooser_with_default(
"Pick a colour", "Pick a colour",
dialog::ColorMode::Byte, dialog::ColorMode::Byte,
@ -981,8 +1095,8 @@ impl MainWindow {
utils::set_color_btn_rgba(prop.translucent_layer_color, &mut f); utils::set_color_btn_rgba(prop.translucent_layer_color, &mut f);
f.redraw(); f.redraw();
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
@ -991,11 +1105,11 @@ impl MainWindow {
let properties = Arc::clone(&self.properties); let properties = Arc::clone(&self.properties);
let sender = self.sender.clone(); let sender = self.sender.clone();
self.translucent_layer_alpha.set_callback(move |f| { self.translucent_layer_alpha.set_callback(move |f| {
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.translucent_layer_color[3] = f.value() as u8; prop.translucent_layer_color[3] = f.value() as u8;
prop.is_saved = false; prop.is_saved = false;
sender.send(DrawMessage::RedrawToBuffer).unwrap(); sender.send_it(DrawMessage::RedrawToBuffer);
sender.send(DrawMessage::Flush).unwrap(); sender.send_it(DrawMessage::Flush);
image.redraw(); image.redraw();
}); });
} }
@ -1004,7 +1118,7 @@ impl MainWindow {
/// Load all iamges in a directory /// Load all iamges in a directory
fn load_dir( fn load_dir(
path: &PathBuf, path: &PathBuf,
imgs: Arc<RwLock<Vec<PathBuf>>>, imgs: Arc<RwLock<Vec<ImageInfo>>>,
file_choice: &mut menu::Choice, file_choice: &mut menu::Choice,
sender: &mpsc::Sender<DrawMessage>, sender: &mpsc::Sender<DrawMessage>,
) { ) {
@ -1014,15 +1128,22 @@ fn load_dir(
.collect::<Vec<fs::DirEntry>>(); .collect::<Vec<fs::DirEntry>>();
files.sort_by_key(|i| i.file_name()); files.sort_by_key(|i| i.file_name());
let mut text = String::new(); let mut text = String::new();
let mut imgs_b = imgs.write().unwrap(); let mut imgs_b = rw_write!(imgs);
*imgs_b = vec![]; *imgs_b = vec![];
for file in files { for file in files {
let path = file.path(); let path = file.path();
if path.extension() == Some(OsStr::new("jpg")) if let Ok(Some(ty)) = infer::get_from_path(&path) {
|| path.extension() == Some(OsStr::new("png")) let mime = ty.mime_type();
{ match ImageType::from_mime(mime) {
ImageType::None => (),
_ => {
text = format!("{}|{}", text, path.file_name().unwrap().to_str().unwrap()); text = format!("{}|{}", text, path.file_name().unwrap().to_str().unwrap());
imgs_b.push(path); imgs_b.push(ImageInfo {
path,
image_type: ImageType::from_mime(mime),
});
}
}
} }
} }
if text.len() == 0 { if text.len() == 0 {
@ -1031,5 +1152,16 @@ fn load_dir(
file_choice.clear(); file_choice.clear();
file_choice.add_choice(&text[1..]); file_choice.add_choice(&text[1..]);
file_choice.set_value(0); file_choice.set_value(0);
sender.send(DrawMessage::Open).unwrap(); sender.send_it(DrawMessage::Open);
sender.send_it(DrawMessage::CheckImage);
}
trait SenderExt {
fn send_it(&self, a: DrawMessage);
}
impl SenderExt for mpsc::Sender<DrawMessage> {
fn send_it(&self, a: DrawMessage) {
self.send(a).expect_log("Program panic!");
}
} }

52
src/result_ext.rs Normal file
View File

@ -0,0 +1,52 @@
/*
This file is part of Post Maker.
Post Maker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Post Maker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Post Maker. If not, see <https://www.gnu.org/licenses/>
*/
use crate::utils;
use std::{fmt::Debug, panic::Location};
pub trait ResultExt<T, E> {
fn expect_log(self, msg: &str) -> T;
fn error_log(&self, msg: &str);
fn warn_log(&self, msg: &str);
}
impl<T, E: Debug> ResultExt<T, E> for Result<T, E> {
#[track_caller]
fn expect_log(self, msg: &str) -> T {
match self {
Ok(v) => v,
Err(e) => {
error!("{}\n{:?}\n{}", msg, e, Location::caller());
utils::show_program_panic(msg);
panic!("[panic]");
}
}
}
#[track_caller]
fn error_log(&self, msg: &str) {
if let Err(e) = self {
error!("{}\n{:?}\n{}", msg, e, Location::caller());
utils::show_alert(msg);
}
}
#[track_caller]
fn warn_log(&self, msg: &str) {
if let Err(e) = self {
warn!("{}\n{:?}", msg, e);
utils::show_alert(msg);
}
}
}

View File

@ -13,16 +13,19 @@
*/ */
use std::{ use std::{
fs, fs::{self, File},
io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use fltk::{button::Button, dialog, enums, prelude::*}; use fltk::{button::Button, enums, prelude::*};
use image::{DynamicImage, GenericImageView, ImageBuffer}; use image::{DynamicImage, GenericImageView, ImageBuffer, ImageEncoder, Pixel};
use imageproc::rect::Rect;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::globals; use crate::globals;
use crate::result_ext::ResultExt;
/// helps cast tupels to f64 /// helps cast tupels to f64
pub(crate) struct Coord(pub(crate) f64, pub(crate) f64); pub(crate) struct Coord(pub(crate) f64, pub(crate) f64);
@ -63,6 +66,47 @@ impl Into<(i32, i32)> for Coord {
} }
} }
impl Into<(usize, usize)> for Coord {
fn into(self) -> (usize, usize) {
(self.0 as usize, self.1 as usize)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct ImageInfo {
pub(crate) path: PathBuf,
pub(crate) image_type: ImageType,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) enum ImageType {
Jpeg,
Png,
Webp,
None,
}
impl ImageType {
pub(crate) fn from_mime(v: &str) -> Self {
match v {
"image/jpeg" | "image/jpg" => Self::Jpeg,
"image/png" => Self::Png,
"image/webp" => Self::Webp,
_ => Self::None,
}
}
pub(crate) fn as_extension(&self) -> String {
match self {
Self::Jpeg => "jpg",
Self::Png => "png",
Self::Webp => "webp",
Self::None => "none",
}
.to_owned()
}
}
/// Contains Image and its buffer(edited image) /// Contains Image and its buffer(edited image)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct ImageContainer { pub(crate) struct ImageContainer {
@ -72,27 +116,18 @@ pub(crate) struct ImageContainer {
} }
impl ImageContainer { impl ImageContainer {
pub(crate) fn new(path: &PathBuf, properties: Arc<RwLock<ImageProperties>>) -> Self { pub(crate) fn new(image_info: &ImageInfo, properties: Arc<RwLock<ImageProperties>>) -> Self {
let img = match image::open(path) { let img = load_image(&image_info);
Ok(i) => i,
Err(e) => {
dialog::alert_default("Failed to open image!");
error!("Failed to open image\n{:?}", e);
panic!("Failed to open image\n{:?}", e);
}
};
let img = DynamicImage::ImageRgb8(img.into_rgb8());
let (width, height): (f64, f64) = Coord::from(img.dimensions()).into(); let (width, height): (f64, f64) = Coord::from(img.dimensions()).into();
let config = globals::CONFIG.read().unwrap(); let config = rw_read!(globals::CONFIG);
let mut prop = properties.write().unwrap(); let mut prop = rw_write!(properties);
prop.path = Some(path.to_owned()); prop.image_info = Some(image_info.to_owned());
prop.original_dimension = (width, height); prop.original_dimension = (width, height);
prop.quote_position = height * config.quote_position_ratio; prop.quote_position = height * config.quote_position_ratio;
prop.subquote_position = height * config.subquote_position_ratio; prop.subquote_position = height * config.subquote_position_ratio;
prop.subquote2_position = height * config.subquote2_position_ratio; prop.subquote2_position = height * config.subquote2_position_ratio;
prop.tag_position = height * config.tag_position_ratio; prop.tag_position = height * config.tag_y_position_ratio;
prop.tag2_position = height * config.tag2_position_ratio; prop.tag2_position = height * config.tag2_position_ratio;
Self { Self {
@ -104,7 +139,7 @@ impl ImageContainer {
/// Resize image /// Resize image
pub(crate) fn apply_resize(&mut self) { pub(crate) fn apply_resize(&mut self) {
let mut prop = self.properties.write().unwrap(); let mut prop = rw_write!(self.properties);
let (width, height) = prop.dimension; let (width, height) = prop.dimension;
let (s_width, s_height) = ((width * 500.0) / height, 500.0); let (s_width, s_height) = ((width * 500.0) / height, 500.0);
@ -116,13 +151,14 @@ impl ImageContainer {
/// Crop Image /// Crop Image
pub(crate) fn apply_crop(&mut self) { pub(crate) fn apply_crop(&mut self) {
let mut prop = self.properties.write().unwrap(); let mut prop = rw_write!(self.properties);
let (original_width, original_height) = prop.original_dimension; let (original_width, original_height) = prop.original_dimension;
let (origina_crop_width, origina_crop_height) = let (origina_crop_width, origina_crop_height) =
croped_ratio(original_width, original_height); croped_ratio(original_width, original_height);
prop.crop_position = Some(( prop.crop_position = Some((
original_width / 2.0 - origina_crop_width / 2.0, (original_width - origina_crop_width) / 2.0,
original_height / 2.0 - origina_crop_height / 2.0, (original_height - origina_crop_height) / 2.0,
)); ));
let (s_width, s_height): (f64, f64) = Coord::from(self.image.dimensions()).into(); let (s_width, s_height): (f64, f64) = Coord::from(self.image.dimensions()).into();
@ -138,12 +174,13 @@ impl ImageContainer {
} }
pub(crate) fn apply_crop_position(&mut self, original_x: f64, original_y: f64) { pub(crate) fn apply_crop_position(&mut self, original_x: f64, original_y: f64) {
let mut prop = self.properties.write().unwrap(); let mut prop = rw_write!(self.properties);
let (original_width, original_height) = prop.original_dimension; let (original_width, original_height) = prop.original_dimension;
prop.crop_position = Some((original_x, original_y)); prop.crop_position = Some((original_x, original_y));
let (s_width, s_height): (f64, f64) = Coord::from(self.image.dimensions()).into(); let (s_width, s_height): (f64, f64) = Coord::from(self.image.dimensions()).into();
let (c_width, c_height) = croped_ratio(s_width, s_height); let (c_width, c_height) = croped_ratio(s_width, s_height);
let (cx, cy) = ( let (cx, cy) = (
(original_x * s_width) / original_width, (original_x * s_width) / original_width,
(original_y * s_height) / original_height, (original_y * s_height) / original_height,
@ -159,7 +196,7 @@ impl ImageContainer {
/// Redraw: Copy image from main image to buffer and draw text and all on it /// Redraw: Copy image from main image to buffer and draw text and all on it
pub(crate) fn redraw_to_buffer(&mut self) { pub(crate) fn redraw_to_buffer(&mut self) {
let prop = self.properties.read().unwrap(); let prop = rw_read!(self.properties);
let mut tmp = self.image.clone(); let mut tmp = self.image.clone();
draw_layer_and_text( draw_layer_and_text(
@ -175,51 +212,58 @@ impl ImageContainer {
&prop.tag2, &prop.tag2,
prop.tag_position, prop.tag_position,
prop.tag2_position, prop.tag2_position,
prop.original_dimension.0,
prop.original_dimension.1, prop.original_dimension.1,
); );
self.buffer = tmp; self.buffer = tmp;
} }
/// Save image anf properities /// Save image and properities
pub(crate) fn save(&self) { pub(crate) fn save(&self) {
let prop = self.properties.read().unwrap(); let prop = rw_read!(self.properties);
let image_info = &prop.image_info;
let path_original = match &prop.path { let (export_path, path_properties, mut original_image) = match image_info {
Some(p) => Path::new(p), Some(p) => (
get_export_image_path(p, &prop.name_prefix),
get_properties_path(p),
load_image(p),
),
None => return, None => return,
}; };
let path_properties = path_original.with_extension("prop"); let config = rw_read!(globals::CONFIG);
let export = path_original.parent().unwrap().join("export").join( let export_format = &config.image_format;
path_original
.with_extension("png")
.file_name()
.unwrap()
.to_str()
.unwrap(),
);
let mut prop = prop.clone(); let mut prop = prop.clone();
prop.path = None; prop.image_info = None;
if let Err(e) = fs::write( fs::write(
&path_properties, &path_properties,
serde_json::to_string(&ImagePropertiesFile::from(&prop)).unwrap(), serde_json::to_string(&ImagePropertiesFile::from(&prop)).unwrap(),
) { )
dialog::alert_default("Failed to save properties!"); .warn_log("Failed to save properties!");
warn!("Failed to save properties!\n{:?}", e);
}
let mut img = image::open(&path_original).unwrap(); let (width, height): (f64, f64) = Coord::from(original_image.dimensions()).into();
let (width, height): (f64, f64) = Coord::from(img.dimensions()).into();
let (crop_x, crop_y) = prop.crop_position.unwrap(); let (crop_x, crop_y) = prop.crop_position.unwrap();
let (crop_width, crop_height) = croped_ratio(width, height); let (crop_width, crop_height) = croped_ratio(width, height);
let mut img = img.crop( let mut img = original_image.crop(
crop_x as u32, crop_x as u32,
crop_y as u32, crop_y as u32,
crop_width as u32, crop_width as u32,
crop_height as u32, crop_height as u32,
); );
if crop_width > config.maximum_width_limit {
let (resize_width, resize_height) = (
config.maximum_width_limit,
height_from_width(config.maximum_width_limit),
);
img = img.resize_exact(
resize_width as u32,
resize_height as u32,
image::imageops::FilterType::Lanczos3,
);
}
draw_layer_and_text( draw_layer_and_text(
&mut img, &mut img,
&prop.translucent_layer_color, &prop.translucent_layer_color,
@ -233,89 +277,113 @@ impl ImageContainer {
&prop.tag2, &prop.tag2,
prop.tag_position, prop.tag_position,
prop.tag2_position, prop.tag2_position,
prop.original_dimension.0,
prop.original_dimension.1, prop.original_dimension.1,
); );
if let Err(e) = img.save_with_format(&export, image::ImageFormat::Png) { let mut output = match File::create(&export_path) {
dialog::alert_default("Failed to export png!"); Ok(a) => a,
warn!("Failed to export png!\n{:?}", e); Err(e) => {
Result::<(), _>::Err(e).warn_log("Failed to write to disk!");
return;
}
};
match export_format {
ImageType::Png => {
let encoder = image::codecs::png::PngEncoder::new_with_quality(
&mut output,
image::codecs::png::CompressionType::Best,
image::codecs::png::FilterType::Sub,
);
let (w, h) = img.dimensions();
encoder
.write_image(&img.into_rgba8(), w, h, image::ColorType::Rgba8)
.warn_log("Failed to export Image!");
}
ImageType::Jpeg => {
let (width, height) = Coord::from(img.dimensions()).into();
let buf = img.into_rgb8();
let mut comp = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_RGB);
comp.set_size(width, height);
comp.set_quality(100.0);
comp.set_smoothing_factor(1);
comp.set_mem_dest();
comp.start_compress();
comp.write_scanlines(&buf);
comp.finish_compress();
match comp.data_to_vec() {
Ok(data) => {
std::fs::write(&export_path, data).warn_log("Failed to export Image!")
}
Err(e) => Result::<(), _>::Err(e).warn_log("Failed to encode image!"),
}
}
_ => (),
} }
} }
pub(crate) fn clone_img(&self) -> Option<PathBuf> { pub(crate) fn clone_img(&self) -> Option<ImageInfo> {
let prop = self.properties.read().unwrap(); let prop = rw_read!(self.properties);
match &prop.path { match &prop.image_info {
Some(path) => { Some(image_info) => {
let name = path.file_stem().unwrap().to_string_lossy(); let stem = image_info.path.file_stem().unwrap().to_string_lossy();
let ext = path.extension().unwrap().to_string_lossy(); let extension = image_info.path.extension().unwrap().to_string_lossy();
let mut i = 1; let mut i = 1;
let mut new_path = path.clone(); let mut new_image_info = image_info.clone();
while new_path.exists() { while new_image_info.path.exists() {
let new_file = format!("{}{}.{}", name, "-copy".repeat(i), ext); let new_file = format!("{}{}.{}", stem, "-copy".repeat(i), extension);
new_path = path.with_file_name(&new_file); new_image_info.path = image_info.path.with_file_name(&new_file);
i += 1; i += 1;
} }
let path_properties = path.with_extension("prop"); let path_properties = get_properties_path(&image_info);
let path_properties_new = new_path.with_extension("prop"); let path_properties_new = get_properties_path(&new_image_info);
if path.exists() { if image_info.path.exists() {
if let Err(e) = fs::copy(path, &new_path) { fs::copy(&image_info.path, &new_image_info.path)
dialog::alert_default("Failed to clone image!"); .warn_log("Failed to clone image!");
warn!("Failed to clone image!\n{:?}", e);
return None;
}
} }
if path_properties.exists() { if path_properties.exists() {
if let Err(e) = fs::copy(path_properties, &path_properties_new) { fs::copy(path_properties, &path_properties_new)
dialog::alert_default("Failed to clone image properties!"); .warn_log("Failed to clone image properties!");
warn!("Failed to clone image properties!\n{:?}", e);
} }
} Some(new_image_info)
Some(new_path)
} }
None => None, None => None,
} }
} }
pub(crate) fn delete(&self) { pub(crate) fn delete(&self) {
let prop = self.properties.read().unwrap(); let prop = rw_read!(self.properties);
let image_info = &prop.image_info;
let path_original = match &prop.path { let (export_path, path_image, path_properties) = match image_info {
Some(p) => Path::new(p), Some(p) => (
get_export_image_path(p, &prop.name_prefix),
Path::new(&p.path),
get_properties_path(p),
),
None => return, None => return,
}; };
let path_properties = path_original.with_extension("prop");
let export = path_original.parent().unwrap().join("export").join(
path_original
.with_extension("png")
.file_name()
.unwrap()
.to_str()
.unwrap(),
);
if path_original.exists() { if path_image.exists() {
if let Err(e) = fs::remove_file(path_original) { fs::remove_file(path_image).warn_log("Failed to delete image!");
dialog::alert_default("Failed to delete image!");
warn!("Failed to delete image!\n{:?}", e);
}
} }
if path_properties.exists() { if path_properties.exists() {
if let Err(e) = fs::remove_file(path_properties) { fs::remove_file(path_properties).warn_log("Failed to delete image properties!");
dialog::alert_default("Failed to delete image properties!");
warn!("Failed to delete image properties!\n{:?}", e);
}
} }
if export.exists() { if export_path.exists() {
if let Err(e) = fs::remove_file(export) { fs::remove_file(export_path).warn_log("Failed to delete exported image!");
dialog::alert_default("Failed to delete exported image!");
warn!("Failed to delete exported image!\n{:?}", e);
}
} }
} }
} }
@ -324,6 +392,7 @@ impl ImageContainer {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct ImagePropertiesFile { pub(crate) struct ImagePropertiesFile {
pub(crate) crop_position: Option<(f64, f64)>, pub(crate) crop_position: Option<(f64, f64)>,
pub(crate) name_prefix: Option<String>,
pub(crate) quote: Option<String>, pub(crate) quote: Option<String>,
pub(crate) subquote: Option<String>, pub(crate) subquote: Option<String>,
pub(crate) subquote2: Option<String>, pub(crate) subquote2: Option<String>,
@ -341,6 +410,7 @@ impl Default for ImagePropertiesFile {
fn default() -> Self { fn default() -> Self {
Self { Self {
crop_position: None, crop_position: None,
name_prefix: None,
quote: None, quote: None,
subquote: None, subquote: None,
subquote2: None, subquote2: None,
@ -360,6 +430,7 @@ impl From<&ImageProperties> for ImagePropertiesFile {
fn from(props: &ImageProperties) -> Self { fn from(props: &ImageProperties) -> Self {
Self { Self {
crop_position: props.crop_position, crop_position: props.crop_position,
name_prefix: Some(props.name_prefix.clone()),
quote: Some(props.quote.clone()), quote: Some(props.quote.clone()),
subquote: Some(props.subquote.clone()), subquote: Some(props.subquote.clone()),
subquote2: Some(props.subquote2.clone()), subquote2: Some(props.subquote2.clone()),
@ -378,10 +449,11 @@ impl From<&ImageProperties> for ImagePropertiesFile {
/// Properties of loaded image /// Properties of loaded image
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct ImageProperties { pub(crate) struct ImageProperties {
pub(crate) path: Option<PathBuf>, pub(crate) image_info: Option<ImageInfo>,
pub(crate) dimension: (f64, f64), pub(crate) dimension: (f64, f64),
pub(crate) original_dimension: (f64, f64), pub(crate) original_dimension: (f64, f64),
pub(crate) crop_position: Option<(f64, f64)>, pub(crate) crop_position: Option<(f64, f64)>,
pub(crate) name_prefix: String,
pub(crate) quote: String, pub(crate) quote: String,
pub(crate) subquote: String, pub(crate) subquote: String,
pub(crate) subquote2: String, pub(crate) subquote2: String,
@ -399,10 +471,11 @@ pub(crate) struct ImageProperties {
impl Default for ImageProperties { impl Default for ImageProperties {
fn default() -> Self { fn default() -> Self {
Self { Self {
path: None, image_info: None,
dimension: (0.0, 0.0), dimension: (0.0, 0.0),
original_dimension: (0.0, 0.0), original_dimension: (0.0, 0.0),
crop_position: None, crop_position: None,
name_prefix: "".to_owned(),
quote: "".to_owned(), quote: "".to_owned(),
subquote: "".to_owned(), subquote: "".to_owned(),
subquote2: "".to_owned(), subquote2: "".to_owned(),
@ -426,10 +499,8 @@ impl ImageProperties {
tag_default: &str, tag_default: &str,
tag2_default: &str, tag2_default: &str,
) { ) {
if let Some(v) = props.crop_position { self.crop_position = props.crop_position;
self.crop_position = Some(v); self.name_prefix = props.name_prefix.unwrap_or("".to_owned());
}
self.quote = props.quote.unwrap_or("".to_owned()); self.quote = props.quote.unwrap_or("".to_owned());
self.subquote = props.subquote.unwrap_or("".to_owned()); self.subquote = props.subquote.unwrap_or("".to_owned());
self.subquote2 = props.subquote2.unwrap_or("".to_owned()); self.subquote2 = props.subquote2.unwrap_or("".to_owned());
@ -442,10 +513,53 @@ impl ImageProperties {
self.tag2_position = props.tag2_position.unwrap_or(self.tag2_position); self.tag2_position = props.tag2_position.unwrap_or(self.tag2_position);
self.translucent_layer_color = props self.translucent_layer_color = props
.translucent_layer_color .translucent_layer_color
.unwrap_or(globals::CONFIG.read().unwrap().color_layer); .unwrap_or(rw_read!(globals::CONFIG).color_layer);
} }
} }
/// Load image as Dynamic Image
fn load_image(image_info: &ImageInfo) -> DynamicImage {
let img = match image_info.image_type {
ImageType::Webp => {
let mut f = File::open(&image_info.path).expect_log("Failed to load image!");
let mut buf = vec![];
f.read_to_end(&mut buf).expect_log("Failed to read image!");
let a = webp::Decoder::new(&buf)
.decode()
.ok_or("")
.expect_log("Failed to decode image!");
a.to_image()
}
ImageType::Jpeg => {
let mut f = File::open(&image_info.path).expect_log("Failed to load image!");
let mut buf = vec![];
f.read_to_end(&mut buf).expect_log("Failed to read image!");
let d = mozjpeg::Decompress::with_markers(mozjpeg::ALL_MARKERS)
.from_mem(&buf)
.expect_log("Failed to decompress image!");
let mut image = d.rgb().expect_log("Failed to convert to rgb image!");
let pixels = image.read_scanlines_flat().unwrap();
let image =
ImageBuffer::from_raw(image.width() as u32, image.height() as u32, pixels).unwrap();
DynamicImage::ImageRgb8(image)
}
ImageType::Png => {
let dec = image::codecs::png::PngDecoder::new(
File::open(&image_info.path).expect_log("Failed to load image!"),
)
.expect_log("Failed to decode image!");
DynamicImage::from_decoder(dec).expect_log("Failed to decode image!")
}
ImageType::None => {
Result::<(), _>::Err("Failed to load image!").expect_log("Unknown format!");
std::process::exit(1);
}
};
DynamicImage::ImageRgb8(img.into_rgb8())
}
/// Draw text and stuffs on image /// Draw text and stuffs on image
fn draw_layer_and_text( fn draw_layer_and_text(
tmp: &mut DynamicImage, tmp: &mut DynamicImage,
@ -458,8 +572,9 @@ fn draw_layer_and_text(
subquote2_position: f64, subquote2_position: f64,
tag: &str, tag: &str,
tag2: &str, tag2: &str,
tag_position: f64, tag_y_position: f64,
tag2_position: f64, tag2_position: f64,
original_width: f64,
original_height: f64, original_height: f64,
) { ) {
let (width, height): (f64, f64) = Coord::from(tmp.dimensions()).into(); let (width, height): (f64, f64) = Coord::from(tmp.dimensions()).into();
@ -475,16 +590,21 @@ fn draw_layer_and_text(
&globals::FONT_QUOTE, &globals::FONT_QUOTE,
size, size,
quote_position, quote_position,
original_width,
original_height, original_height,
true,
quote, quote,
); );
let size = subquote_from_height(height); let size = subquote_from_height(height);
draw_multiline_mid_string( draw_multiline_mid_string(
tmp, tmp,
&globals::FONT_SUBQUOTE, &globals::FONT_SUBQUOTE,
size, size,
subquote_position, subquote_position,
original_width,
original_height, original_height,
true,
subquote, subquote,
); );
let size = subquote2_from_height(height); let size = subquote2_from_height(height);
@ -493,7 +613,9 @@ fn draw_layer_and_text(
&globals::FONT_SUBQUOTE2, &globals::FONT_SUBQUOTE2,
size, size,
subquote2_position, subquote2_position,
original_width,
original_height, original_height,
true,
subquote2, subquote2,
); );
@ -503,7 +625,9 @@ fn draw_layer_and_text(
&globals::FONT_TAG2, &globals::FONT_TAG2,
size, size,
tag2_position, tag2_position,
original_width,
original_height, original_height,
false,
tag2, tag2,
); );
@ -518,8 +642,9 @@ fn draw_layer_and_text(
imageproc::drawing::draw_text_mut( imageproc::drawing::draw_text_mut(
tmp, tmp,
image::Rgba([255, 255, 255, 255]), image::Rgba([255, 255, 255, 255]),
(width * 0.99 - text_width) as u32, (width * rw_read!(globals::CONFIG).tag_x_position_ratio - text_width) as i32,
((tag_position * height) / original_height + index as f64 * (text_height * 1.2)) as u32, ((tag_y_position * height) / original_height + index as f64 * (text_height * 1.2))
as i32,
rusttype::Scale::uniform(size as f32), rusttype::Scale::uniform(size as f32),
&globals::FONT_TAG, &globals::FONT_TAG,
line, line,
@ -533,24 +658,142 @@ pub(crate) fn draw_multiline_mid_string(
font: &rusttype::Font, font: &rusttype::Font,
size: f64, size: f64,
position: f64, position: f64,
original_width: f64,
original_height: f64, original_height: f64,
boxed: bool,
text: &str, text: &str,
) { ) {
let (mut box_width, mut box_height) = (0.0, 0.0);
let (width, height): (f64, f64) = Coord::from(tmp.dimensions()).into(); let (width, height): (f64, f64) = Coord::from(tmp.dimensions()).into();
let has_line_spacing = rw_read!(globals::CONFIG).line_spacing;
for (index, line) in text.lines().enumerate() { for (index, line) in text.lines().enumerate() {
let (text_width, text_height) = let (text_width, text_height) =
measure_line(font, line, rusttype::Scale::uniform(size as f32)); measure_line(font, line, rusttype::Scale::uniform(size as f32));
if text_width > box_width {
box_width = text_width;
}
box_height += text_height
* if index == 0 || !has_line_spacing {
1.0
} else {
1.12
};
let (x, y) = (
(width - text_width) / 2.0,
(position * height) / original_height + index as f64 * (text_height * 1.12),
);
if !boxed || !rw_read!(globals::CONFIG).draw_box_around_quote {
imageproc::drawing::draw_text_mut( imageproc::drawing::draw_text_mut(
tmp, tmp,
image::Rgba([255, 255, 255, 255]), image::Rgba([255, 255, 255, 100]),
((width - text_width) / 2.0) as u32, x as i32,
((position * height) / original_height + index as f64 * (text_height * 1.15)) as u32, y as i32,
rusttype::Scale::uniform(size as f32), rusttype::Scale::uniform(size as f32),
font, font,
line, line,
); );
} }
}
if boxed && rw_read!(globals::CONFIG).draw_box_around_quote {
draw_box(
tmp,
box_width,
box_height,
position,
original_width,
original_height,
);
draw_multiline_mid_string(
tmp,
font,
size,
position,
original_width,
original_height,
false,
text,
);
}
}
/// Draws box around text.
fn draw_box(
tmp: &mut DynamicImage,
box_width: f64,
box_height: f64,
position: f64,
original_width: f64,
original_height: f64,
) {
if box_width <= 0.0 {
return;
}
let (width, height): (f64, f64) = Coord::from(tmp.dimensions()).into();
let (delta_x, delta_y) = (width / original_width, height / original_height);
let (x_gap, y_gap) = (30.0 * delta_x, 10.0 * delta_y);
let (x, y) = (
((width - box_width) / 2.0 - x_gap) as u32,
((position * height) / original_height - y_gap) as u32,
);
let (w, h) = (
(box_width + x_gap * 2.0) as u32,
(box_height + y_gap * 2.0) as u32,
);
if x >= width as u32 || y >= height as u32 {
return;
}
let mut buff = tmp.crop(x, y, w, h);
let layer = DynamicImage::ImageRgba8(ImageBuffer::from_fn(w, h, |_, _| {
image::Rgba([20, 22, 25, 80])
}));
image::imageops::overlay(&mut buff, &layer, 0, 0);
buff = buff.blur(15.0);
let (dx, dy) = (20.0 * delta_x, 20.0 * delta_y);
let mut shadow = DynamicImage::new_rgba8(w + (dx * 2.0) as u32, h + (dy * 2.0) as u32);
imageproc::drawing::draw_hollow_rect_mut(
&mut shadow,
Rect::at(dx as i32, dy as i32).of_size(w, h),
image::Rgba([30, 30, 30, 255]),
);
shadow = shadow.blur(5.0 * delta_x as f32);
image::imageops::overlay(tmp, &shadow, x as i64 - dx as i64, y as i64 - dy as i64);
image::imageops::overlay(tmp, &buff, x as i64, y as i64);
let mut color = buff.get_pixel(0, 0).to_rgba();
color.blend(&buff.get_pixel(0, buff.height() - 1).to_rgba());
color.blend(
&buff
.get_pixel(buff.width() - 1, buff.height() - 1)
.to_rgba(),
);
color.blend(&buff.get_pixel(buff.width() - 1, 0).to_rgba());
imageproc::drawing::draw_hollow_rect_mut(
tmp,
Rect::at(
x as i32 - (delta_x * 1.0) as i32,
y as i32 - (delta_x * 1.0) as i32,
)
.of_size(w + (delta_x * 2.0) as u32, h + (delta_x * 2.0) as u32),
color.clone(),
);
color.blend(&image::Rgba([0, 0, 0, 2]));
imageproc::drawing::draw_hollow_rect_mut(
tmp,
Rect::at(x as i32, y as i32).of_size(w, h),
color.clone(),
);
} }
/// Get size of text to draw on image /// Get size of text to draw on image
@ -571,6 +814,95 @@ pub(crate) fn measure_line(
Coord::from((width, height)).into() Coord::from((width, height)).into()
} }
/// path of properties files
pub(crate) fn get_properties_path(image_info: &ImageInfo) -> PathBuf {
let img = &image_info.path;
let image_name: String = image_info
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
let mut occurance = 0;
let image_name = image_name
.chars()
.into_iter()
.rev()
.map(|c| {
if occurance == 0 && c == '.' {
occurance = 1;
'-'
} else {
c
}
})
.collect::<Vec<char>>()
.into_iter()
.rev();
let image_name = format!("{}.prop", String::from_iter(image_name));
let default_path = img.with_file_name(&image_name);
if default_path.exists() {
return default_path;
}
let path = img.with_extension("prop");
if path.exists() {
match std::fs::copy(&path, &default_path) {
Ok(_) => std::fs::remove_file(&path).warn_log("Failed to delete depricated prop file"),
Err(e) => Result::<(), _>::Err(e).warn_log("Failed to copy depricated prop file"),
}
}
default_path
}
/// path of properties files
pub(crate) fn get_export_image_path(image_info: &ImageInfo, name_prefix: &str) -> PathBuf {
let config = rw_read!(globals::CONFIG);
let export_format = &config.image_format;
let image_name = image_info
.path
.file_name()
.unwrap_or_default()
.to_string_lossy();
let mut occurance = 0;
let image_name = image_name
.chars()
.into_iter()
.rev()
.map(|c| {
if occurance == 0 && c == '.' {
occurance = 1;
'-'
} else {
c
}
})
.collect::<Vec<char>>()
.into_iter()
.rev();
let image_name = format!(
"{}{}.{}",
name_prefix,
String::from_iter(image_name),
export_format.as_extension()
);
let expost_dir = image_info.path.parent().unwrap().join("export");
if !expost_dir.exists() {
fs::create_dir(&expost_dir).expect_log("Failed to create export folder!");
}
let export = expost_dir.join(&image_name);
export
}
/// small hack because 0,0,0 rgb, because can't be set on fltk theme /// small hack because 0,0,0 rgb, because can't be set on fltk theme
pub(crate) fn set_color_btn_rgba(rgba: [u8; 4], btn: &mut Button) { pub(crate) fn set_color_btn_rgba(rgba: [u8; 4], btn: &mut Button) {
let [mut r, g, b, _] = rgba; let [mut r, g, b, _] = rgba;
@ -580,6 +912,16 @@ pub(crate) fn set_color_btn_rgba(rgba: [u8; 4], btn: &mut Button) {
btn.set_color(enums::Color::from_rgb(r, g, b)); btn.set_color(enums::Color::from_rgb(r, g, b));
} }
/// Check if image is too small
pub(crate) fn is_too_small(width: f64, height: f64) -> bool {
let (crop_width, _) = croped_ratio(width, height);
if crop_width < rw_read!(globals::CONFIG).minimum_width_limit {
true
} else {
false
}
}
/// Get required size to crop image as per image ratio /// Get required size to crop image as per image ratio
pub(crate) fn croped_ratio(width: f64, height: f64) -> (f64, f64) { pub(crate) fn croped_ratio(width: f64, height: f64) -> (f64, f64) {
if width > width_from_height(height) { if width > width_from_height(height) {
@ -591,37 +933,58 @@ pub(crate) fn croped_ratio(width: f64, height: f64) -> (f64, f64) {
/// Get required witdh to crop image from height as per image ratio /// Get required witdh to crop image from height as per image ratio
pub(crate) fn width_from_height(height: f64) -> f64 { pub(crate) fn width_from_height(height: f64) -> f64 {
let (w, h) = globals::CONFIG.read().unwrap().image_ratio; let (w, h) = rw_read!(globals::CONFIG).image_ratio;
(w * height) / h (w * height) / h
} }
/// Get required height to crop image from width as per image ratio /// Get required height to crop image from width as per image ratio
pub(crate) fn height_from_width(width: f64) -> f64 { pub(crate) fn height_from_width(width: f64) -> f64 {
let (w, h) = globals::CONFIG.read().unwrap().image_ratio; let (w, h) = rw_read!(globals::CONFIG).image_ratio;
(h * width) / w (h * width) / w
} }
/// Get required quote size for crop image from height as per image ratio /// Get required quote size for crop image from height as per image ratio
pub(crate) fn quote_from_height(height: f64) -> f64 { pub(crate) fn quote_from_height(height: f64) -> f64 {
(height * globals::CONFIG.read().unwrap().quote_font_ratio) / 5000.0 (height * rw_read!(globals::CONFIG).quote_font_ratio) / 5000.0
} }
/// Get required subquote size for crop image from height as per image ratio /// Get required subquote size for crop image from height as per image ratio
pub(crate) fn subquote_from_height(height: f64) -> f64 { pub(crate) fn subquote_from_height(height: f64) -> f64 {
(height * globals::CONFIG.read().unwrap().subquote_font_ratio) / 5000.0 (height * rw_read!(globals::CONFIG).subquote_font_ratio) / 5000.0
} }
/// Get required subquote2 size for crop image from height as per image ratio /// Get required subquote2 size for crop image from height as per image ratio
pub(crate) fn subquote2_from_height(height: f64) -> f64 { pub(crate) fn subquote2_from_height(height: f64) -> f64 {
(height * globals::CONFIG.read().unwrap().subquote2_font_ratio) / 5000.0 (height * rw_read!(globals::CONFIG).subquote2_font_ratio) / 5000.0
} }
/// Get required tag size for crop image from height as per image ratio /// Get required tag size for crop image from height as per image ratio
pub(crate) fn tag_from_height(height: f64) -> f64 { pub(crate) fn tag_from_height(height: f64) -> f64 {
(height * globals::CONFIG.read().unwrap().tag_font_ratio) / 5000.0 (height * rw_read!(globals::CONFIG).tag_font_ratio) / 5000.0
} }
/// Get required tag2 size for crop image from height as per image ratio /// Get required tag2 size for crop image from height as per image ratio
pub(crate) fn tag2_from_height(height: f64) -> f64 { pub(crate) fn tag2_from_height(height: f64) -> f64 {
(height * globals::CONFIG.read().unwrap().tag2_font_ratio) / 5000.0 (height * rw_read!(globals::CONFIG).tag2_font_ratio) / 5000.0
}
pub(crate) fn show_message(msg: &str) {
let a = rw_read!(globals::MAIN_SENDER);
if let Some(a) = &*a {
a.send(crate::AppMessage::Message(msg.to_owned()));
}
}
pub(crate) fn show_alert(msg: &str) {
let a = rw_read!(globals::MAIN_SENDER);
if let Some(a) = &*a {
a.send(crate::AppMessage::Alert(msg.to_owned()));
}
}
pub(crate) fn show_program_panic(msg: &str) {
let a = rw_read!(globals::MAIN_SENDER);
if let Some(a) = &*a {
a.send(crate::AppMessage::ProgramPanicMessage(msg.to_owned()));
}
} }