Compare commits

..

24 Commits

Author SHA1 Message Date
doryan 62b7036933 I'm too lazy to commit to the latest files 2024-08-15 23:37:34 +04:00
doryan 3d896c7061 feat(file): rename from signal_reduce_input_utils.rs to signal_reduce_utils.rs 2024-08-15 23:34:48 +04:00
doryan e8c73b75a1 fix(css): textview paddings 2024-08-15 23:33:11 +04:00
doryan b4fb37539c feat(size): change window size by default 2024-08-15 23:32:42 +04:00
doryan f9699180b9 feat(todo): remove todo 2024-08-15 23:32:22 +04:00
doryan 839694edc1 feat(props): add properties for optimize calculates 2024-08-15 23:32:05 +04:00
doryan cedb844826 fix(processing bug): add '\n' string literal to avoid rejecting the right input binary code with a break line 2024-08-15 23:31:09 +04:00
doryan a7cf11737a feat(values): move definition values by default to separated file as function 2024-08-15 23:27:36 +04:00
doryan f78001167a feat(condition): remove unnecessary condition 2024-08-15 23:24:52 +04:00
doryan 11ba24d7ed refactor(parse_field): rewrite fields parser util
Field parser has been rewritten as the signal reduce page uses a Entry
inputs instead TextView.
2024-08-15 23:23:12 +04:00
doryan e9764cf0e8 feat(rename): rename signal_reduce_inpur_utils.rs to signal_reduce_utils.rs 2024-08-15 23:18:55 +04:00
doryan ac9e8953a9 feat(imports): add imports 2024-08-15 23:17:26 +04:00
doryan 4e872ddcff feat(imports): update imports 2024-08-15 23:17:00 +04:00
doryan 8e8cecd463 feat(fn): remove redundant arguments 2024-08-15 23:15:39 +04:00
doryan cd697b9f5d feat(setter): add PasswordEntryBuilder
Trait TextViewSetters has been removed as there is already an analogue
of this trait in existence
2024-08-14 19:02:25 +04:00
doryan 7116b565de feat(setter): remove old trait
Trait TextViewSetters has been removed as there is already an analogue
of this trait in existence
2024-08-14 19:01:34 +04:00
doryan 7282045c98 feat(method): add get_component() method
This method let get Box whose children are a label and a TextView/Entry
2024-08-14 18:57:14 +04:00
doryan f57aed1fb8 feat(method): remove old build() method 2024-08-14 18:55:00 +04:00
doryan 2e1f4b6a12 feat(frame): remove frame from Input component 2024-08-14 18:54:13 +04:00
doryan d3327a7c36 feat(input): add generic type for other input components 2024-08-14 18:53:27 +04:00
doryan 06439585eb feat(input): add macro for generation build_*() method code 2024-08-14 18:51:44 +04:00
doryan 418e916c0d feat(crate): add external crate gio 2024-08-14 18:49:00 +04:00
doryan 23bfd10e2b feat(css): add style for TextView and generic css
Generic CSS includes styles for text_view.css and info_bar.css
2024-08-14 18:48:36 +04:00
doryan 269b30312d test 2024-08-12 23:49:40 +04:00
18 changed files with 277 additions and 249 deletions

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::sync::LazyLock;
use crate::{
controller::view_utils::{hamming_code_input_utils::*, input_utils::*},
controller::view_utils::{hamming_code_utils::*, input_utils::*},
model::{models::*, Result},
};
@ -24,7 +24,7 @@ pub fn hamming(raw_input: String, mode: HammingMode) -> Result<String> {
let prepared_input: String = processing_input(&raw_input);
let (first_condition, second_condition): (bool, bool) =
check_correct_binary_code(&raw_input, &prepared_input, length_of_code);
check_correct_binary_code(&prepared_input, length_of_code);
if raw_input.is_empty() {
Err("Введите код.".into())

View File

@ -1,3 +1,7 @@
use crate::model::Frequency;
use gio::{prelude::Cast, ListStore};
#[allow(non_snake_case)]
pub fn reactive_resistance_of_capacitor(Cm: f64, L: f64, f: f64) -> f64 {
if f == 0.0 || Cm == 0.0 || L == 0.0 {
@ -29,3 +33,19 @@ pub fn coef_of_signal_reduce(Vs: f64, V: f64) -> f64 {
Vs / V
}
}
pub fn set_default_values(model: &ListStore) {
for number in (0..=100).step_by(5) {
if number == 0 {
model.append(&Frequency::new(1.0));
} else if (number >= 70 && number % 10 == 0) || (number < 70 && number % 5 == 0) {
model.append(&Frequency::new(number as f64));
}
}
}
pub fn find_by_frequency_value(model: &ListStore, new_elem: &Frequency) -> Option<u32> {
model.find_with_equal_func(|elem| {
elem.downcast_ref::<Frequency>().unwrap().frequency() == new_elem.frequency()
})
}

View File

@ -25,10 +25,10 @@ pub fn start_hamming_algorithm(input: &TextView, state: bool) -> Result<String>
hamming(parsed_input, operation)
}
pub fn check_correct_binary_code(input: &str, prepared_input: &str, l: usize) -> (bool, bool) {
pub fn check_correct_binary_code(prepared_input: &str, l: usize) -> (bool, bool) {
let first_condition: bool = prepared_input.len() % l == 0;
let second_condition: bool = input.chars().all(|c| c == '1' || c == '0' || c == ' ');
let second_condition: bool = prepared_input.chars().all(|c| c == '1' || c == '0');
(first_condition, second_condition)
}

View File

@ -9,8 +9,8 @@ const ASCII_ZERO_CHAR_POSITION: u8 = 48;
pub fn processing_input(input: impl Into<String>) -> String {
input
.into()
.split_ascii_whitespace()
.filter(|&x| !x.is_empty())
.split(&[' ', '\n'][..])
.filter(|x| !x.is_empty())
.fold(String::new(), |c: String, n: &str| c + n)
}

View File

@ -1,4 +1,3 @@
pub mod hamming_code_input_utils;
pub mod hamming_code_utils;
pub mod input_utils;
pub mod signal_reduce_input_utils;
pub mod signal_reduce_utils;

View File

@ -1,49 +0,0 @@
use gtk4 as gtk;
use std::str::FromStr;
use gtk::{
prelude::{TextBufferExt, TextViewExt},
TextBuffer,
};
use crate::{
model::{models::SignalReduce, Error, Result},
view::components::input::Input,
};
pub fn get_error_message(error: Error) -> Option<&'static str> {
match error.to_string().as_str() {
"cannot parse float from empty string" => Some("Вы не ввели данные в поле/-я"),
"invalid float literal" => Some("Вы ввели не корректные данные в поле/-я"),
_ => None,
}
}
pub fn parse_fields(all_inputs: Vec<Input>) -> Result<SignalReduce> {
let mut values: [f64; 6] = [0.0; 6];
for (i, input) in all_inputs.iter().enumerate() {
let input_text_buffer: TextBuffer = input.clone().get_input().buffer();
let extracted_value = f64::from_str(
input_text_buffer
.text(
&input_text_buffer.start_iter(),
&input_text_buffer.end_iter(),
false,
)
.as_str()
.trim(),
)?;
values[i] = extracted_value;
}
Ok(SignalReduce {
length: values[0],
wire_resistance: values[1],
wire_capacity: values[2],
source_resistance: values[3],
source_voltage: values[4],
frequency: values[5],
})
}

View File

@ -0,0 +1,128 @@
use gtk4 as gtk;
use std::str::FromStr;
use crate::{
model::{models::SignalReduce, Error, Frequency, Result},
model_utils::signal_reducer::*,
view::components::input::Input,
};
use gtk::{
prelude::{Cast, CastNone, EditableExt, ListItemExt, ObjectExt, WidgetExt},
ColumnView, Entry, Label, ListItem, SignalListItemFactory,
};
pub fn get_error_message(error: Error) -> Option<&'static str> {
match error.to_string().as_str() {
"cannot parse float from empty string" => Some("Вы не ввели данные в поле/-я"),
"invalid float literal" => Some("Вы ввели не корректные данные в поле/-я"),
_ => None,
}
}
pub fn parse_fields(all_inputs: Vec<Input<Entry>>) -> Result<SignalReduce> {
let mut values: [f64; 6] = [0.0; 6];
for (i, input) in all_inputs.iter().enumerate() {
let input_text_buffer = input.get_input();
let extracted_value = f64::from_str(input_text_buffer.text().as_str().trim())?;
values[i] = extracted_value;
}
Ok(SignalReduce {
length: values[0],
wire_resistance: values[1],
wire_capacity: values[2],
source_resistance: values[3],
source_voltage: values[4],
frequency: values[5],
})
}
#[inline]
pub fn update_column_view(column_view: &ColumnView) {
column_view.hide();
column_view.show();
}
#[inline]
pub fn column_view_setup_factory(_factory: &SignalListItemFactory, list_item: &ListItem) {
list_item
.downcast_ref::<ListItem>()
.expect("Needs to be ListItem")
.set_child(Some(&Label::new(None)));
}
pub fn column_view_bind_factory(
_factory: &SignalListItemFactory,
list_item: &ListItem,
values: SignalReduce,
label: &str,
) {
let cell_value = list_item
.downcast_ref::<ListItem>()
.expect("Needs to be ListItem")
.item()
.and_downcast::<Frequency>()
.expect("The item has to be an `IntegerObject`.");
let cell_label = list_item
.downcast_ref::<ListItem>()
.expect("Needs to be ListItem")
.child()
.and_downcast::<Label>()
.expect("The child has to be a `Label`.");
cell_value
.bind_property("frequency", &cell_label, "label")
.sync_create()
.build();
if cell_value.reactive_resist() == 0.0 {
cell_value.set_reactive_resist(reactive_resistance_of_capacitor(
values.wire_capacity * 10f64.powi(-12),
values.length,
cell_value.frequency() * 10f64.powi(6),
));
}
if cell_value.full_resistance() == 0.0 {
cell_value.set_full_resistance(full_resistance_of_capacitor(
cell_value.reactive_resist(),
values.source_resistance,
values.wire_resistance,
values.length,
));
}
if cell_value.signal_source_voltage() == 0.0 {
cell_value.set_signal_source_voltage(
voltage_from_signal_source(
values.source_voltage * 10f64.powi(-3),
cell_value.reactive_resist(),
cell_value.full_resistance(),
) * 1000.0,
);
}
match label {
"f, МГц" => {
cell_label.set_label(&cell_value.frequency().to_string());
}
"Xc, Ом" => {
cell_label.set_label(format!("{0:.1$}", cell_value.reactive_resist(), 6).as_str());
}
"Vп, мВ" => {
cell_label
.set_label(format!("{0:.1$}", cell_value.signal_source_voltage(), 6).as_str());
}
"ζ" => {
let coef: f64 =
coef_of_signal_reduce(values.source_voltage, cell_value.signal_source_voltage());
cell_label.set_label(format!("{0:.1$}", coef, 6).as_str());
}
_ => {}
}
}

View File

@ -1,5 +1,7 @@
use gtk4 as gtk;
extern crate gio;
use gtk::prelude::*;
mod controller;

View File

@ -31,6 +31,12 @@ pub struct SignalReduce {
pub struct Frequency {
#[property(get, set)]
frequency: Cell<f64>,
#[property(get, set)]
reactive_resist: Cell<f64>,
#[property(get, set)]
full_resistance: Cell<f64>,
#[property(get, set)]
signal_source_voltage: Cell<f64>,
}
#[glib::derived_properties]

View File

@ -26,8 +26,6 @@ pub struct InfoBarBuilder {
button: ButtonBuilder,
}
// TODO: Develop a method to safely mutate static.
// Not necessary.
static mut TASKS_QUEUE: VecDeque<JoinHandle<()>> = VecDeque::new();
//

View File

@ -5,12 +5,46 @@ use gtk::{prelude::*, *};
pub type InputLabel = String;
macro_rules! build_for {
( $(($comp:ty,$name:ident)),* ) => {
$(
pub fn $name(self, input_height: Option<i32>) -> Input<$comp> {
let input_component = Box::new(Orientation::Vertical, 0);
let input_label = Label::builder()
.halign(self.align.horizontal)
.valign(self.align.vertical)
.set_margin(self.margins)
.label(self.label)
.build();
let mut input_builder = <$comp>::builder()
.set_margin(MarginData::EqualsMargin(6));
if let Some(height) = input_height {
input_builder = input_builder.height_request(height);
}
let input = input_builder.build();
input_component.append(&input_label);
input_component.append(&input);
Input {
component: input_component,
input: input.clone(),
input_label: input_label.clone(),
}
}
)*
};
}
#[derive(Clone, Debug)]
pub struct Input {
pub struct Input<I> {
component: Box,
input: TextView,
input: I,
input_label: Label,
input_frame: Frame,
}
pub struct InputBuilder {
@ -19,7 +53,7 @@ pub struct InputBuilder {
margins: MarginData,
}
impl Product<InputBuilder, Box> for Input {
impl<I> Product<InputBuilder, Box> for Input<I> {
fn builder() -> InputBuilder {
InputBuilder {
align: Alignment {
@ -36,13 +70,13 @@ impl Product<InputBuilder, Box> for Input {
}
}
impl Input {
pub fn get_input(&self) -> &TextView {
&self.input
impl<I> Input<I> {
pub fn get_component(&self) -> &Box {
&self.component
}
pub fn get_frame(&self) -> &Frame {
&self.input_frame
pub fn get_input(&self) -> &I {
&self.input
}
pub fn get_label(&self) -> &Label {
@ -69,36 +103,5 @@ impl InputBuilder {
self
}
pub fn build(self, monospace: bool, wrap_mode: WrapMode, input_height: i32) -> Input {
let input_component = Box::new(Orientation::Vertical, 0);
let text_view_label = Label::builder()
.halign(self.align.horizontal)
.valign(self.align.vertical)
.set_margin(self.margins)
.label(self.label)
.build();
let text_view_input = TextView::builder()
.monospace(monospace)
.height_request(input_height)
.set_text_view_margin(MarginData::EqualsMargin(6))
.wrap_mode(wrap_mode)
.build();
let text_view_input_frame = Frame::builder()
.child(&text_view_input)
.set_margin(MarginData::MultipleMargin((0, 5, 0, 5)))
.build();
input_component.append(&text_view_label);
input_component.append(&text_view_input_frame);
Input {
component: input_component,
input: text_view_input.clone(),
input_frame: text_view_input_frame.clone(),
input_label: text_view_label.clone(),
}
}
build_for!((TextView, build), (Entry, build_entry));
}

View File

@ -6,7 +6,7 @@ use crate::{
components::{info_bar::InfoBar, input::Input, wrapper::*},
properties::*,
},
view_utils::{hamming_code_input_utils::start_hamming_algorithm, input_utils::clearing},
view_utils::{hamming_code_utils::start_hamming_algorithm, input_utils::clearing},
};
use gtk::{glib::clone, prelude::*, *};
@ -14,20 +14,25 @@ use gtk::{glib::clone, prelude::*, *};
pub fn hamming_code_page(wrapper: &Box) {
let info_bar = InfoBar::get_instance();
let input_code = Input::builder()
let input_code = Input::<TextView>::builder()
.label("Поле ввода для кода:")
.margins(MarginData::EqualsMargin(6))
.align(Alignment::new(Align::Start, Align::Start))
.build(true, WrapMode::Word, 64);
.build(Some(64));
let output_code = Input::builder()
let output_code = Input::<TextView>::builder()
.label("Результат:")
.margins(MarginData::EqualsMargin(6))
.align(Alignment::new(Align::Start, Align::Start))
.build(true, WrapMode::Word, 64);
.build(Some(64));
output_code.get_input().set_editable(false);
for input in [&input_code, &output_code] {
input.get_input().set_monospace(true);
input.get_input().set_wrap_mode(WrapMode::Word);
}
let clear_input_button = Button::builder()
.set_align(Alignment::new(Align::Fill, Align::Fill))
.label("Очистка полей")

View File

@ -1,29 +1,24 @@
use std::cell::Cell;
use std::rc::Rc;
use std::{cell::Cell, rc::Rc};
use crate::{
model::{builder_traits::Product, models::SignalReduce, Frequency},
model_utils::signal_reducer::{
coef_of_signal_reduce, full_resistance_of_capacitor, reactive_resistance_of_capacitor,
voltage_from_signal_source,
},
model_utils::signal_reducer::{find_by_frequency_value, set_default_values},
view::{
components::{info_bar::InfoBar, input::Input},
properties::*,
},
view_utils::signal_reduce_input_utils::{get_error_message, parse_fields},
view_utils::signal_reduce_utils::*,
};
use glib::clone;
extern crate gio;
use gio::ListStore;
use gtk::{
prelude::{BoxExt, ButtonExt, Cast, CastNone, GridExt, ListItemExt, ListModelExt, SorterExt},
Align, WrapMode, *,
prelude::{BoxExt, ButtonExt, Cast, GridExt, SorterExt},
Align, *,
};
use gtk4 as gtk;
pub fn signal_reducing_page(wrapper: &Box) {
@ -31,9 +26,6 @@ pub fn signal_reducing_page(wrapper: &Box) {
let info_bar = InfoBar::get_instance();
let (input_height, monospace, input_wrapping): (i32, bool, WrapMode) =
(24, true, WrapMode::Word);
let input_block: Grid = Grid::new();
input_block.set_column_homogeneous(true);
@ -46,37 +38,31 @@ pub fn signal_reducing_page(wrapper: &Box) {
let input_labels: [&str; 6] = ["l, м:", "Rм, Ом", "Cм, пФ:", "Rи, Ом:", "Vи, мВ", "f, мГц:"];
let all_inputs: Vec<Input> = input_labels
let all_inputs: Vec<Input<Entry>> = input_labels
.iter()
.map(move |label| {
Input::builder()
.enumerate()
.map(|(index, label)| {
let elem = Input::<Entry>::builder()
.label(label)
.margins(MarginData::EqualsMargin(6))
.align(input_label_alignment)
.build(monospace, input_wrapping, input_height)
.build_entry(None);
let row = index as i32 / 3;
input_block.attach(elem.get(), (index as i32) - (3 * row), row, 1, 1);
elem
})
.collect();
for (id, elem) in all_inputs.iter().enumerate() {
let row = id as i32 / 3;
input_block.attach(elem.get(), (id as i32) - (3 * row), row, 1, 1);
}
let calculate_button = Button::builder().label("Расчитать").build();
let result_table_headers_labels: [&str; 4] = ["f, МГц", "Xc, Ом", "Vп, мВ", "ζ"];
let model = ListStore::new::<Frequency>();
let model_for_events = model.clone();
for number in (0..=100).step_by(5) {
if number == 0 {
model.append(&Frequency::new(1.0));
} else if (number >= 70 && number % 10 == 0) || (number < 70 && number % 5 == 0) {
model.append(&Frequency::new(number as f64));
}
}
set_default_values(&model);
let numeric_sorter = CustomSorter::new(|a, b| {
let a = a.downcast_ref::<Frequency>().unwrap().frequency();
@ -85,12 +71,11 @@ pub fn signal_reducing_page(wrapper: &Box) {
a.total_cmp(&b).into()
});
let sorted_model = SortListModel::new(Some(model), Some(numeric_sorter.clone()));
let sorted_model = SortListModel::new(Some(model.clone()), Some(numeric_sorter.clone()));
let selection_model = NoSelection::new(Some(sorted_model));
let selection_model = NoSelection::new(Some(sorted_model.clone()));
let result_table = ColumnView::builder()
.reorderable(true)
.show_row_separators(true)
.model(&selection_model)
.build();
@ -98,78 +83,19 @@ pub fn signal_reducing_page(wrapper: &Box) {
result_table.connect_activate(clone!(
#[strong]
numeric_sorter,
move |_, _| {
numeric_sorter.changed(SorterChange::Different);
}
move |_, _| numeric_sorter.changed(SorterChange::Different)
));
for label in result_table_headers_labels {
let factory = SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
list_item
.downcast_ref::<ListItem>()
.expect("Needs to be ListItem")
.set_child(Some(&Label::new(None)));
});
factory.connect_setup(column_view_setup_factory);
let values_for_factory = values.clone();
factory.connect_bind(move |_, list_item| {
let cell_value = list_item
.downcast_ref::<ListItem>()
.expect("Needs to be ListItem")
.item()
.and_downcast::<Frequency>()
.expect("The item has to be an `IntegerObject`.");
let cell_label = list_item
.downcast_ref::<ListItem>()
.expect("Needs to be ListItem")
.child()
.and_downcast::<Label>()
.expect("The child has to be a `Label`.");
let result_values = values_for_factory.get();
let reactive_resist: f64 = reactive_resistance_of_capacitor(
result_values.wire_capacity * 10f64.powi(-12),
result_values.length,
cell_value.frequency() * 10f64.powi(6),
);
let full_resistance: f64 = full_resistance_of_capacitor(
reactive_resist,
result_values.source_resistance,
result_values.wire_resistance,
result_values.length,
);
let signal_source_voltage: f64 = voltage_from_signal_source(
result_values.source_voltage * 10f64.powi(-3),
reactive_resist,
full_resistance,
) * 1000.0;
match label {
"f, МГц" => {
cell_label.set_label(&cell_value.frequency().to_string());
}
"Xc, Ом" => {
cell_label.set_label(format!("{0:.1$}", reactive_resist, 6).as_str());
}
"Vп, мВ" => {
cell_label.set_label(format!("{0:.1$}", signal_source_voltage, 6).as_str());
}
"ζ" => {
let coef: f64 =
coef_of_signal_reduce(result_values.source_voltage, signal_source_voltage);
cell_label.set_label(format!("{0:.1$}", coef, 6).as_str());
}
_ => {}
}
});
factory.connect_bind(clone!(
#[strong]
values,
move |factory, list| column_view_bind_factory(factory, list, values.get(), label)
));
let column = ColumnViewColumn::builder()
.title(label)
@ -193,28 +119,23 @@ pub fn signal_reducing_page(wrapper: &Box) {
calculate_button.connect_clicked(clone!(
#[strong]
model_for_events,
model,
#[strong]
result_table,
move |_| match parse_fields(all_inputs.clone()) {
Ok(results) => {
values.set(results);
let new_elem = &Frequency::new(values.get().frequency);
let new_elem = Frequency::new(values.get().frequency);
let exist_elem_pos = model_for_events.find_with_equal_func(|elem| {
elem.downcast_ref::<Frequency>().unwrap().frequency() == new_elem.frequency()
});
let exist_elem_pos = find_by_frequency_value(&model, &new_elem);
match exist_elem_pos {
Some(_) => {
if exist_elem_pos.is_some() {
info_bar.set_text_label(Some("Данная частота уже была задана."));
info_bar.show_infobar(5u64);
}
None => {
model_for_events.append(&Frequency::new(values.get().frequency));
model_for_events.items_changed(0, 0, 1);
model_for_events.items_changed(1, 1, 0);
}
} else {
model.append(&new_elem);
update_column_view(&result_table)
}
}
Err(error) => {

View File

@ -23,7 +23,6 @@ pub enum MarginData {
*/
#[allow(dead_code)]
pub struct Size {
pub width: i32,
pub height: i32,
@ -44,27 +43,6 @@ pub trait Setters {
fn set_align(self, align: Alignment) -> Self;
}
pub trait TextViewSetters {
fn set_text_view_margin(self, margin: MarginData) -> Self;
}
impl TextViewSetters for TextViewBuilder {
fn set_text_view_margin(self, margin: MarginData) -> Self {
match margin {
MarginData::EqualsMargin(margin) => self
.top_margin(margin)
.left_margin(margin)
.bottom_margin(margin)
.right_margin(margin),
MarginData::MultipleMargin(margins) => self
.top_margin(margins.0)
.left_margin(margins.1)
.bottom_margin(margins.2)
.right_margin(margins.3),
}
}
}
/**
* Macros
*/
@ -97,7 +75,7 @@ macro_rules! impl_setters {
}
impl_setters! {ButtonBuilder, EntryBuilder, TextViewBuilder,
BoxBuilder, SwitchBuilder, FrameBuilder, LabelBuilder}
BoxBuilder, SwitchBuilder, FrameBuilder, LabelBuilder, PasswordEntryBuilder}
#[allow(dead_code)]
impl Size {

2
src/view/styles/base.css Normal file
View File

@ -0,0 +1,2 @@
@import url("info_bar.css");
@import url("text_view.css");

View File

@ -9,7 +9,8 @@ use gtk::{
pub fn load_css() {
let style_provider = CssProvider::new();
style_provider.load_from_path(Path::new("./src/view/styles/info_bar.css"));
style_provider.load_from_path(Path::new("./src/view/styles/base.css"));
style_context_add_provider_for_display(
&Display::default().expect("Could not connect to a display"),

View File

@ -0,0 +1,14 @@
textview {
border-radius: 5px;
padding: 7px 8px;
background: #E6E6E6;
outline-offset: 2px;
outline: 2px solid #81ABDF00;
transition-duration: .15s;
transition-timing-function: ease-in-out;
}
textview:focus-within {
outline-offset: -2px;
outline: 2px solid #81ABDF;
}

View File

@ -53,8 +53,8 @@ pub fn ui(application: &adw::Application) {
let window = ApplicationWindow::builder()
.title("Комплексная программа для лаб. работ")
.width_request(700)
.height_request(400)
.width_request(800)
.height_request(600)
.application(application)
.child(&application_box)
.build();