Русский
Русский
English
Статистика
Реклама

Gui

Из песочницы Как верстать веб-интерфейсы быстро, качественно и интересно

24.06.2020 18:08:11 | Автор: admin

image


Всем привет! Давно хотел и наконец написал небольшую книжку бодрое пособие по своей профессиональной области: актуальным подходам к разметке интерфейсов, экранному дизайну и доступности. Она о моем оригинальном подходе к созданию GUI, препроцессорам CSS (для объективности, немного и об альтернативных подходах), и его эффективном практическом использовании с javascript и популярными реактивными компонентными фреймворками Vue и React. Материал представлен аккуратно последовательно, но безумно интенсивно и динамично ничего лишнего или даже слишком подробного для того чтобы увлеченный и подготовленный читатель не потерял интереса и проглотил на одном дыхании. С другой стороны, текст, достаточно сложный ближе к концу, и на всем протяжении густо насыщенный идеями, ссылками на технологии и подходы поэтому, очевидно, будет на вырост начинающим. Но, в любом случае, как и если вы только начали интересоваться данной тематикой, так и если уже давно занимаетесь веб-дизайном, версткой и программированием фронтенда вам может быть полезно на него взглянуть.


Мотивация


Я уже много лет занимаюсь, прежде всего, разметкой интерфейсов, их оформлением и поведением, экранами и GUI. Участвовал на позиции верстальщика в небольших командах, стартапах, а также реализовал некоторое количество проектов самостоятельно как фрилансер, дизайнер и верстальщик. За это время я накопил некоторый опыт по специфическим проблемам в данной области, которым мне очень хочется поделиться.


Начну с некоторого огульно обобщающего и, поэтому, несколько провокационного наблюдения, вброса: большинство, не только начинающих, но даже опытных программистов испытывают своеобразное предубеждение-стереотип о некой несерьезности верстки интерфейса как отрасли деятельности в веб-индустрии верстка это не программирование!. Очень многие считают что заниматься разметкой некруто и скучно для серьезного специалиста. И как следствие уделяют предмету мало своего внимания и времени, имеют мало реального релевантного опыта. Проще говоря, большинство разработчиков не любит и не умеет верстать. И уже и как причина и как следствие зачастую сами подходы, технологии и архитектура используемые для организации GUI даже на серьезных коммерческих проектах отставляют желать лучшего, не отвечают реалиям современной веб-индустрии, устарели и недостаточно эффективны. Неудачные, якобы временные, слабые решения и дальнейшие бесконечные быстрые фиксы, кривые кряки наслаиваются друг-на-друга, кодовая база неоправданно распухает и становится все менее связной и контролируемой. Шаблоны, или ныне в эру компонентных фреймворков компоненты, стили и логика для них часто и закономерно превращаются в невнятную и крайне излишнюю по сути свалку, густой лес, джунгли с очевидно неподъемной ценой рефакторинга и масштабирования. Очевидно, что такое состояние системы может легко приводить к серьезным затруднениям, снижению темпов или даже срыву сроков и, ирония как раз в том, что в реальной коммерческой ситуации, авторам подобной громоздкой и неуклюжей системы, все равно придется потратить еще больше времени на то, чтобы, по крайней мере, исправить все оплошности и несовершенства разметки, оформления и логики для них, но делать это придется в изначально плохо организованном коде, по замкнутому кругу только увеличивая беспорядок и вероятность сбоев. С того момента как вы перестаете быть джуном, ваша ответственность состоит не только в том чтобы закрыть все баги на трекере и навернуть как можно скорее фичи, любой ценой. Надежная и легко поддерживаемая система продукт который вы продаете бизнесу.


Реальный жизненный кейс: будучи начинающим специалистом я работал удаленно в одном приятном стартапе. Когда проект запустили, после презентации многие присутствовавшие на ней высказались о том, что кегль основного текста и полей ввода в интерфейсе мелковат. Получив задачу в трекере, я потратил всего пару минут, поправив одну переменную своей системы чтобы поднять кегль на всех нужных полях и контролах, и еще 15 минут чтобы удостовериться что ничего действительно не сломалось ни на одном шаблоне. Ваша система изначально должна быть написана так, и только так, чтобы ее было можно легко скорректировать и улучшить, поддерживать и расширять. По-настоящему лаконичный и выразительный качественный код невероятно мощно экономит время и нервы даже если вы работаете с проектом в одиночку. Кроме того, уважаемые авторитеты в коммерческом программировании утверждают [см. Роберт Мартин Чистая архитектура] что то что сделано изначально плохо в реальности, не только никогда не будет переписано, но и приводит к постоянному крутому росту стоимости дальнейшей доставки нового функционала, а в перспективе способно полностью блокировать прогресс по проекту!


Хорошее понимание возможностей и ограничений современных браузерных технологий очень желательно и полезно не только для веб-разработчиков, но и для гуманитариев-дизайнеров проектирующих GUI. Часто даже востребованный специалист, выдающий приятный стиль и не делающих серьезных ошибок по UХ, не может внятно ответить на простейшие технические вопросы о своем дизайне при сдаче макета на верстку, то есть не понимает что рисует.


В этом пособии я стараюсь в достаточно доступной форме дать некоторые полезные идеи о современном экранном дизайне и эффективных способах его реализации для браузера. Я буду приводить конкретные примеры кода, но не ради специфических моментов и подробностей реализации, а, прежде всего, иллюстрируя и подчеркивая общие мысли, идеи, практические подходы, годные для написания крупных сложных современных проектов. Этот текст не еще один занудный скучный учебник, а, скорее, методичка углубленного спецкурса. Материал будет подаваться максимально интенсивно и насыщенно, и новичок может не справиться сходу с примерами кода или даже содержанием некоторых разделов. Не паникуйте. Важнее всего в нашей отрасли научиться развиваться самостоятельно в выбранном направлении. Этот текст скорее призван помочь вам понять что гуглить дальше?.


Кому будет полезен текст?


Начинающим. Вы минимально освоились со спецификациями HTML и CSS, начали пробовать JavaScript, успешно сверстали свои первые страницы, макеты и хотите большего, хотите получить некий дружеский пинок под зад, который поможет вам осознать дальнейшие горизонты, выйти на новый уровень и начать получать больше удовольствия и удовлетворения, как и от самой этой деятельности, так и от ее результатов.


Не верстающим как бог. Вы давно занимаетесь веб-программированием, но верстаете нехотя и по-старинке, при этом, осознаете свой пробел в знаниях и навыках, созрели для того чтобы отбросить оковы лени и невежества, переломить ситуацию ведь на каждом проекте разработка GUI для вас это скучная трудная рутина и мука.


Дизайнерам. Вы веб-дизайнер, но хотите начать верстать.


Эта работа о программировании дизайна и дизайне программных продуктов для фронтенда. Стоит иметь ввиду, что она написана, скорее, веб-дизайнером для веб-программистов, чем веб-программистом для веб-дизайнеров, хотя это и неточно. Я решил разделить текст на две части:


Препроцессор


В первой части представлена мощная практическая метода, дающая яркие идеи и четкие рекомендации по организации разработки стилей и разметки. Материал выбран таким образом, чтобы последовательно, но с крутой интенсивностью ввести, возможно даже, только еще минимально овладевшего HTML и CSS читателя, в мир удивительных возможностей препроцессора. Хочется показать, как можно стремительно организовать рабочую кухню, необходимую и достаточную для того чтобы эффективно создавать качественную адаптивную разметку. В этой части почти ничего не говорится о самом javascript, для того, чтобы, если читатель не освоил еще в достаточной степени язык программирования, но уже начал верстать все равно смог бы все понять и начать внедрять на практике.


Препроцессор, JavaScript и фреймворки


Вторая часть не такая злая. Она, например, показывает в увлекательной форме некоторые углубленные интересные кейсы современного экранного дизайна и вопросов связанных с доступностью веб-приложений. В ней речь идет о практическом применении подходов к препроцессору представленных в первой части, в связке с javascript для GUI, особенно в контексте компонентных фреймворков.


Левон Гамбарян.
Июнь 2020 года.



Препроцессор




Простейший пример плохого кода


Безобразного кода с каждым днем становится все больше, несмотря на то, что больнее всего страдают от этого сами авторы, горе-разработчики. И если определенную функциональность или рендер можно покрыть тестами, то ошибки оформления выявляются только ручками.


Также, просто парадоксально, что все программисты слышали о простых основополагающих принципах качественного программирования KISS и SOLID, но подавляющее большинство напрочь забывает о них, когда речь заходит об организации оформления, представления веб-интерфейса.


В реальной ситуации заказчик, покупатель вашего кода в любой момент может захотеть внести любые правки, и вы должны быть максимально к этому готовы.


Давайте посмотрим на самый простейший пример плохого кода на CSS:


/* Примитивнейший пример обычного плохого кода на CSS *//* Где-нибудь в файлах стилей: */.selector--1 {  width: 200px;  height: 200px;  border: 1px solid #ADADAD;  border-radius: 3px;  /* ... и дальше еще огромное количество самых разных правил */}.selector--2 {  width: 200px;  height: 400px;  border: 1px solid #ADADAD;  border-radius: 3px;  /* ... и дальше еще огромное количество самых разных правил */}

Не делайте так больше почти никогда! ))) Почему? Код валидный, в браузере все по макету, да и все именно так обычно и пишут. Но все и не верстают как бог, правильно? В контексте любого проекта чуть большего чем совсем крохотный, подобный код плохой и чреват проблемами в будущем. Он конкретен и невыразителен, излишен, его сложно модифицировать и переиспользовать.


А как надо?


На сегодняшний день стандартной практикой для разработки CSS-кода является использование Препроцессора, который отнюдь не превращает формальную спецификацию CSS в язык программирования, но дает синтаксису необходимую силу и мощь, актуальные возможности и эффективность. Если говорить совсем просто, то без него, очень многие требования современного веб-дизайна иначе было бы крайне затруднительно реализовать, особенно в масштабе крупных проектов.


Справедливости ради, нужно упомянуть, что последние годы, в связи с стремительным ростом популярности компонентных js-фреймворков и их подходов, все больше сторонников набирают также различные CSS-in-JS-реализации (например: Styled Components). Скоро, вероятно, можно будет спокойно использовать переменные в самом CSS (CSS Custom Properties). Тема холиварная, существуют контексты и ситуации когда подобный CSS-in-JS подход может оказаться более оправданным и изящным, без сомнения. И даже существует масса реалистичных кейсов когда проще всего будет действительно обойтись несколькими наборами правил на CSS, а любое его расширение будет излишним. Но в общем случае, в реальной коммерческой практике, имхо, для верстки сложных дизайнов и интерфейсов удобнее и эффективнее всего сейчас использовать любой препроцессор, и, шок даже с компонентным фреймворком, дальше я планирую показать как именно это лучше всего делать. Препроцессоры дают максимум возможностей и позволяют стремиться к максимальной выразительности и переиспользуемости. Вот во что превратился бы плохой код выше в SCSS-синтаксисе, наверное самого популярного на сегодняшний день препроцессора Sass:


// В @/src/scss/utils/_variables.scss:$colors__border: #adadad;$border-radius: 3px;// В @/src/scss/utils/_placeholders.scss:%border-block {  border: 1px solid $colors__border;  border-radius: $border-radius;}// В @/src/scss/utils/_mixins.scss:@mixin size($width, $height) {  width: $width;  height: $height;}// В любом месте проекта:.selector {  $selector--1__size: 200px;  $selector--2__width: 200px;  $selector--2__height: 400px;  &--1,  &--2 {    @extend %border-block;    /* ... включение других сущностей препроцессора      и специфическиих правил общих для селекторов */  }  &--1 {    @include size($selector--1__size, $selector--1__size);    /* ... включение других сущностей препроцессора      и специфических правил уникальных для селектора */  }  &--2 {    @include size($selector--2__width, $selector--2__height);    /* ... включение других сущностей препроцессора      и специфических правил уникальных для селектора */  }}

Точно тоже самое легко сделать и на, кажется, недооцененном, но очень удачном Stylus совершенно не важно какой именно расширенный синтаксис вы используете, главное как и зачем. Очень много раз мне приходилось видеть плохой чужой код написанный якобы для препроцессора, видимо, потому что так сейчас модно, но, на самом деле, практически ничем не отличающийся от кода CSS. Не делайте так! Препроцессор дает нам крайне ценную возможность абстрагировать общие качества гайдлайна, стиль и основанные на нем частные стили, организовать их намного более выразительно и лаконично, легко модифицировать и переиспользовать при необходимости.


В данном, вырванном из контекста, но, при этом, вполне жизненном примере, кажется, что кода препроцессора сильно больше. Он еще и раскидан по нескольким разным файлам, что, как будто, еще все усложняет. Зачем так напрягаться, а? Прежде всего, привычка начинать писать разметку с переменных и обобщений очевидно грамотная. Перестаньте плодить изолированные глухие кряки с магическими числами, начните применять абстракцию! Делайте хорошо сразу, потому что вы почти никогда и ничего не переделаете потом, на самом деле. Когда наш стартап наконец взлетит, и как раз во многом из-за такого отношения он может и не взлететь, в результате. Чем детализированнее и сложнее ваш интерфейс, его дизайн, тем больше строк и времени вы будете экономить просто оптимизируя общие наборы правил, переиспользуя стили. Кроме того, поддерживать код и, тем более, вносить серьезные изменения будет на порядок проще.


Первый пример демонстрирует что на начальных этапах развития проекта хорошо продуманного кода препроцессора может быть даже визуально несколько больше, чем неорганизованного, внутренне плоского, скучного CSS. Но давайте разберем очень часто встречающийся кейс, в котором мы очевидно сразу сильно экономим много трафика и явно оптимизируем возможную поддержку. Такое очень часто встречается: нам нужно создать большое количество, предположим 20 штук модификаторов одного селектора квадратной иконки размеров в 100 пикселей и поставить в них нужные картинки в бекграунд. В Sass мы можем написать цикл с интерполяцией для создания селектора модификатора и указания пути до ресурса. И хотя такая синтаксическая возможность не является чем-то идейно решающе важным на практике она экономит кучу времени и повышает качество жизни на работе:


// В @/src/scss/utils/_variables.scss:// Paths$images__path--root: "../../assets/images/";// Sizes $icons__size: 100px;// Views$icons: 20;// В любом месте проекта (в папке В @/src/scss/project/):.icon {  // корректируем путь до картинок  $image-path: $image_path--root + "icons/";  @include size($icons__size, $icons__size); // эта примесь уже создана выше  @for $i from 1 through $icons {    &.icon--#{$i} {      background: url("#{$image-path}icon--#{$i}.svg") center center no-repeat;    }  }}

Пример предполагает что в вашем проекте следующая структура:


. src    assets      images         icon--1.svg         icon--2.svg         ...    sscs       project         ...       utils          _mixins.scss          _variables.scss

Теперь в шаблонах мы можем использовать:


<div class="icon icon--1"></div>

Если вы желаете чтобы картинки были с осмысленными именами можете перебирать список:


.icon {  $image-path: $image_path--root + "icons/";  $images: "name1", "name2", "name3"; // Список имен  @include size($icons__size, $icons__size);  @each $image in $images {    &.icon--#{$image} {      background: url("#{$image-path}#{$image}.svg") center center no-repeat;    }  }}

Ну и раз уж мы упомянули интерполяцию, необходимо вспомнить еще один простой кейс, который сейчас часто нужен и в котором вам она точно пригодится посчитать с переменной:


.selector {  $width: 100px;  width: calc(100vw - #{$width});}

Используйте на полную мощность и изучайте подробно свои ключевые инструменты. Применяйте свой интеллект и фантазию. В хорошем коде препроцессора наборы правил не повторяются и какдый селектор содержит только небольшое количество объявлений. Повторяющиеся и похожие наборы, большое количество самых разных объявлений в селекторах признак плохо организованного кода. Пока бесконечный поиск и замена остаются вашим главным и единственным инструментом рефакторинга кода разметки вы живете в каменном веке веб-технологий и еще даже не начинали верстать!)


Абстрагируй все!


Что такое дизайн, если совсем кратко? Дизайн это гайдлайн строгая система, набор стилевых правил и ограничений, перечень констант, аксиом и отношений в разметке и оформлении интерфейса, которым он неукоснительно должен соответствовать. Задача верстальщика в том чтобы правильно воспринять эту систему и максимально эффективно перевести ее с языка графических прототипов в работающий по заявленным требованиям код.


Поэтому маниакально абстрагируй все что только можно. Все что должно и может быть переиспользовано и любые конкретные значения, те, которые, когда-нибудь, но, в принципе, могут измениться. К примеру, на Stylus:


// В @/src/stylus/utils/variables.styl:$colors = {  mint: #44c6a8,  // ... другие конкретные значения цветов}// Создаем "основной цвет", абстрагируясь от конкретного цвета$colors['primary'] = $colors.mint// ... другие "функциональные" цвета

Любое имеющее глобальное значение и потенциально переиспользуемое качество гайдлайна и дизайна должно быть отражено в файле переменных препроцессора. Теперь в любом месте где потребуется предоставить основной брендовый цвет:


.selector  color $colors.primary

Очевидно, что если весь остальной код будет аккуратно использовать правильную переменную просто по щелчку пальцев возможно изменить этот основной цвет по всему интерфейсу! Все это логично и закономерно приводить нас к идее некой общей стилевой базы, медиатора единого стиля оформления интерфейса. Такую глобальную абстракцию легко способен предоставить препроцессор, и ее, вероятно, будет удобно использовать даже для оформления изолированных компонентов.


Структура и стилевая база препроцессора


Дальше я покажу определенную логичную и простую структуру организации файлов препроцессора, которую использую на проектах сам. Но вы можете что-то делать совсем иначе и, в результате, прийти к совсем другой, удобной именно для ваших методов работы системе. Важно не следовать каким-то зазубренным до автоматизма теориям и якобы лучшим практикам, а гибко и к месту применять возможности, и даже, вероятно все время немного экспериментировать, стараясь расти и меняться к лучшему. Категорически важно только то, что ваши стили должны быть действительно всегда четко и понятно организованы.


На практике во многих командах вытесняют и игнорируют выгоду от более продвинутых и аккуратных подходов к разметке. Ведь это требует определенных затрат на коммуникацию. Например, очень часто файлы стилей даже на препроцессоре тупо и незамысловато складируются в кучу и практически никак не связаны друг-с-другом. Признайтесь себе наконец честно: даже аккуратная подробная компонентность, хоть и позволяет решить проблему на самом примитивном физическом уровне, она вообще не решает ее глобально, на уровне абстракций. Наоборот компонентность даже еще несколько затрудняет решение, так как строится именно на противоположных глобальности препроцессора, изолирующих подходах. Ваша куча это по прежнему невыразительная невнятная неповоротливая куча, просто теперь она еще и разделена на множество подобных и глобально излишних куч, поменьше.


Но давайте уже организуем препроцессор, если с SCSS:


. src    sscs       core // обшие и компилируемые сущности препроцессора         _animations.scss // keyframes         _base.scss // минимальная нормализация основных HTML-элементов         _grid.scss // сетки         _typography.scss // типографика         _utilities.scss // быстрые удобные классы-утилиты для включения прямо в разметку       libraries // папка с файлами стилизаций сторонних модулей         _modal.scss - например какая-нибудь готовая модаль       project // стили конкретного проекта         _elements.scss // отдельные простые элементы-компоненты         _fixes.scss // этот файл всегда должен быть практически пустой, и предназначен только для редких общеизвестных "собственных проблем браузеров"         _layout.scss - стили общей для всех страниц GUI-обертки над контентом интерфейса         _widgets.scss - сложные составные комбинации простых элементов-компонентов       utils // обшие и некомпилируемые основные сущности препроцессора         _functions.scss // на практике нужны крайне редко         _mixins.scss // параметризируемые и способные принимать контент примеси-микстуры         _placeholders.scss // повторяющиеся наборы правил - растворы         _variables.scss // самый важный файл с переменными )       _main.scss // точка сборки всех стилей препроцессора       _stylebase.scss // стилевая база

То есть, на самом деле порядок сборки всей кухни имеет значение, конечно же:


// В @/src/scss/_stylebase.scss:// Stylebase////////////////////////////////////////////////////////////////////////////////////////////////////////////// Uncompiled kitchen@import "./utils/_functions";@import "./utils/_variables";@import "./utils/_mixins";@import "./utils/_placeholders";// Core base normal style and common utils@import "./core/_animations";@import "./core/_typography";@import "./core/_base";@import "./core/_grid";@import "./core/_utilities";// В @/src/scss/_main.scss:// Project styles////////////////////////////////////////////////////////////////////////////////////////////////////////////// Stylebase for components@import "_stylebase";// App styles@import "./project/_fixes";@import "./project/_elements";@import "./project/_widgets";@import "./project/_layout";/* External libraries customization */@import "./libraries/_modal";

Итак, стилевой базой мы будем называть некое основное ядро стилей, доступный всем остальным компонентам системы общий код препроцессора. Более детально, он состоит из условно двух разных видов файлов:


  1. Растворяемые при компиляции инструменты-помощники, сущности позволяющие генерировать лаконичный, оптимальный, связный код:


    1. функции
    2. переменные
    3. параметризуемые примеси
    4. включения-плейсхолдеры

  2. Компилируемые глобальные стили:


    1. анимации keyframes
    2. типографика
    3. базовая нормализация основных HTML-элементов
    4. сетки
    5. утилитарные классы-помощники для разметки


В папки @/src/scss/project и @/src/scss/libraries вы можете добавлять файлы по необходимости.


Удобно держать подобную минимальную кухню наготове в ваших стартовых проектах, чтобы с самого начала работы быстро настраивать стилизацию под конкретные требования и гайдлайн.


У меня вот, например, можно посмотреть есть различные такие заготовки-болванки для быстрого старта на разных комбинациях актуальных технологий:



Читать книжку дальше можно пройдя по ссылке: Как верстать веб-интерфейсы быстро, качественно и интересно.

Подробнее..

Из песочницы Эволюция Material Design для AvaloniaUI

18.11.2020 00:09:51 | Автор: admin

image


Material.Avalonia быстрый способ стилизовать под Material Desing приложение, написанное на AvaloniaUI кросс-платформенном XAML фреймворке для .NET.


Примерно с год назад на Харбе уже была статья о нашей библиотеке, однако с этого момента изменилось многое: мы избавились от встроенных тем, переделали стандартные и добавили свои элементы управления.


Сейчас наше демо приложение выглядит вот так:


image


Еще примеры приложений, использующих Material.Avalonia


Начало использования


Сначала установим необходимый Nuget пакет:


dotnet add package Material.Avalonia

После этого изменим файл App.xaml, если нам нужно стилизовать все приложение. Либо, если нужно изменить оформление только одного окна или другого элемента управления, то вместо Application.Styles (Application.Resources) у нас будут Window.Styles или UserControl.Styles соответственно.


<Application ...             xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"             ...>    <Application.Resources>        <themes:BundledTheme BaseTheme="Light" PrimaryColor="Teal" SecondaryColor="Amber"/>    </Application.Resources>    <Application.Styles>        <StyleInclude Source="avares://Material.Avalonia/Material.Avalonia.Templates.xaml" />    </Application.Styles></Application>

Все, после этого все наше приложение будет использовать стили Material Design.
Однако, не все элементы управления уже стилизованы. Если некоторые из них не работают, то измените Application.Styles следующим образом:


<Application.Styles>    <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>    <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>    <StyleInclude Source="avares://Material.Avalonia/Material.Avalonia.Templates.xaml" /></Application.Styles>

Данное изменение добавит стандартные темы контролов Avalonia "под" темы Material.Avalonia.


Темы


За последние 2 месяца мы полностью переписали темы, и, избавились наконец от предварительно заготовленных наборов тем.


Теперь для настройки темы по умолчанию нужно модифицировать свойства BundledTheme в App.xaml.


Базовая тема может быть светлой Light, темной Dark и наследуемой Inherit. Последний вариант будет пытаться получить тему, используемую системой, но в данный момент это еще не реализовано.


BundledTheme поддерживает задание всех, доступных в Material Design, "стандартных" цветов.


Смена цвета, например на Teal, из кода происходит подобным образом:


var paletteHelper = new PaletteHelper();var theme = paletteHelper.GetTheme();theme.SetPrimaryColor(SwatchHelper.Lookup[(MaterialColor) PrimaryColor.Teal]);paletteHelper.SetTheme(theme);

Однако, через код можно задать вообще любой пользовательский цвет из RGBA.
Про работу с цветами темы я напишу отдельную статью.


Кастомные контролы


Card

Спецификация на сайте Material Design


image


Для Card можно менять размер тени используя Attached Property:


<styles:Card assists:ShadowAssist.ShadowDepth="Depth1">...</styles:Card>

ColorZone

Цветовая зона позволяет легко переключать цвета фона и переднего плана из выбранной палитры Material Design или пользовательских цветов.


image


<styles:ColorZone Margin="4" Padding="8" Mode="Accent">Accent</styles:ColorZone>

FloatingButton

image


<styles:FloatingButton Content="+"  /><styles:FloatingButton Content="My long text" />

Тени


В Avalonia поддержка теней "из коробки" ограничена заданием BoxShadows для Border.
Однако уже реализован AttachedProperty ShadowAssist, который может быть использован для Card, FloatingButton и Border.


Когда для Border задается ShadowAssist.ShadowDepth то он самостоятельно корректирует BoxShadows для соответствия выбранному уровню ShadowDepth.


Так-же есть ShadowAssist.Darken, позволяющий затемнять уже существующую тень и делающий это с анимацией. Таким образом сделано изменение тени, при наведение на кнопки.


Демонстрация Card с разными ShadowDepth


image


Многое уже сделано, еще больше запланировано.
Ознакомиться или помочь с разработкой можно на GitHub
Скачать пакет на NuGet


Issue/PR и просто отзывы категорически приветствуются.
Поддержку от разработчиков Avalonia и всех сочувствующих можно получить в Telegram (ru) и Gitter (en), а документация по стилизации элементов управления доступна тут.

Подробнее..

Почему клавиатура всегда быстрее мыши

27.05.2021 10:19:54 | Автор: admin

Тепловая карта с клавиатуры высокоинтеллектуальных программистов, источник: r/ProgrammerHumor/

Много говорилось об удовольствии, которое испытываешь при работе в консоли. Это не случайно: так задумано отцами-основателями Unix. Возникает странное ощущение, что это самый правильный способ взаимодействия с компьютером.

Более того, такое же приятное ощущение возникает при использовании горячих клавиш в любых приложениях, даже с графическим интерфейсом.

В чём же дело?

Экзотический манипулятор


Изначально трекболы использовались для особых профессиональных приложений: например, для систем управления радарами. Мышь как перевёрнутый трекбол, накрытый коробкой, придумали в 1980-е годы. В то время программисты не совсем понимали, зачем компьютеру мышь, если код пишется в виде машинных команд, а взаимодействие с мейнфреймом целиком происходит в консоли.

Необычные манипуляторы с колёсиком стоили в районе 400 долларов. Затем вышел революционный компьютер Apple Lisa один из первых ПК с графическим интерфейсом. Компания Apple демпинговала она снизила стоимость манипулятора до 25 долларов и сделала сексуальный дизайн с одной кнопкой. Мышь из профессионального аксессуара превратилась в массовый гаджет.


Apple Lisa. Очень элегантный дизайн для своего времени

С тех пор мышь и GUI стали прочно ассоциироваться с компьютерами Apple и модным оконным интерфейсом.

Вскоре появилась Windows. Затем первые игры с поддержкой мыши, например, для более точного движения персонажа Возникает впечатление, что компьютерные игры постоянно стимулировали технический прогресс, в том числе производительность CPU, GPU, ёмкость накопителей, популярность мышей и других экзотических манипуляторов.

Геймеры вечно недовольны текущей производительностью персональных компьютеров и непрерывно требуют от индустрии наращивать мощность железа. Если посмотреть, кто собирает самые производительные конфигурации ПК и покупает самые мощные версии десктопных процессоров, то это будут сотни любителей рекордных FPS, парочка учёных с машинным обучением и один программист, который каждый день что-то билдит и компилирует. Да и то, трём последним персонажам компьютер нужен конкретно для работы, так что они не всегда вкладываются собственными деньгами, как геймеры.

Если бы не компьютерные игры, то производители процессоров могли сосредоточиться исключительно на серверных CPU. В самом деле, армия бухгалтеров, экономистов и прочих офисных клерков спокойно посидят на компьютерах 20-летней давности, которые их полностью устраивают. Они вообще не в курсе, какое железо стоит внутри процессора (так они называют системный блок). Зато не отрывают руку от любимой мышки. Отними у офисного клерка мышь и он будет несколько минут тупо пялиться в монитор и бессмысленно дёргать рукой, не в силах совершить ни одного полезного действия, словно под седативными веществами.

В наше время редко встретишь компьютер без мыши. А вот удовольствие от работы в консоли осталось.

На форумах и в блогах пользователи Linux делятся любимыми однострочниками и другими трюками, которые повышают эффективность рабочего процесса. Есть даже отличный сайт с голосованием за однострочники.

Крутые однострочники


Вот некоторые примеры интересного использования программ Linux.

ps aux | convert label:@- process.png

Команда конвертирует выдачу из консоли в изображение. Например, чтобы поделиться с коллегами в виде скриншота. Сама идея скриншотить текст некоторым кажется концептуально неправильной, но в некоторых случаях так просто быстрее и удобнее. В наше время с распространением смартфонов и планшетов скриншоты становятся чуть ли не стандартом для копирования текста с экрана, что немного печалит, если воспринимать это как намёк на закат универсальных компьютеров

Примечание. Утилита convert входит в пакет ImageMagick, так что нужно сначала его установить.

А вообще, текст из консоли можно быстро запостить через интернет-сервис вроде termbin.com (это как pastebin, только для консоли):

ps aux | nc termbin.com 9999

Как обычно, с алиасом для частого использования:

alias tb='nc termbin.com 9999'

Следующая:

curl ipinfo.io

Это если хотите узнать свой внешний IP-адрес через сервис ipinfo.io.

git log --format='%aN' | sort -u

Очень удобная команда, если работаете над опенсорсным проектом и хотите посмотреть контрибуторов.

history | awk '{print $2}' | sort | uniq -c | sort -rn | head

Отсортированный список самых часто запускаемых команд (тоже полезно добавить в алиасы, чтобы запускать в пару нажатий).

ls -d */

Листинг только директорий. Полезная команда, которой тоже хорошо бы присвоить алиас для быстрого использования.

du -hs */ | sort -hr | head

Эта команда показывает только 10 крупнейших директорий в текущем каталоге.

ss -p

Просмотр, какие приложения потребляют трафик (утилиты iftop и nethogs дают более подробную информацию).

rm -f !(test.txt)

Команда удаляет из директории все файлы, кроме одного, указанного в скобках. Это работает после включения расширенной глобуляции в баше (shopt -s extglob).

python3 -m http.server

Запускает http-сервер и начинает отдавать файлы. Удобно, если хотите пошарить какой-то html-файл по сети.

screen -S the-screen-name

Создание экран-сессии.

screen -x the-screen-name

Подключение к экран-сессии. Такую сессию можно принудительно создать у пользователя после его авторизации в системе и наблюдать, что происходит на его экране. Полезно, если кто-то подключился к вашему серверу, а вы хотите посмотреть, чем он занимается.

Утилита screen поставляется по умолчанию со многими дистрибутивами Linux, хотя не со всеми.

alias copy='xclip -i -selection clipboard'

cat file.txt | copy

Копирование файла в буфер обмена, когда первый однострочник прописан как алиас copy в баше.

sudo !!

Запустить последнюю команду под рутом, если в предыдущей команде вы забыли набрать sudo. У этой команды первое место в рейтинге однострочников.

Конечно, здесь просто примеры. Прелесть линуксовой консоли в том, что можно самому придумать такую последовательность команд, которую никто вообще никогда не использовал, но вам нужен именно этот конвейер в данной ситуации.

Горячие клавиши как наследие консоли


В графических программах под Windows тоже зачастую хочется сделать работу горячими клавишами, не трогая этот эппловский манипулятор. На самом деле использование горячих клавиш очень ускоряет рабочий процесс в любой программе.

Алиасы bash служат той же цели: выполнить команду с наименьшим количеством усилий, то есть с наименьшим количеством нажатий клавиш.

Взаимодействие с машиной у опытного специалиста происходит практически со световой скоростью. Скорость ограничивается только временем обработки команд на CPU и скоростью вывода текста в консоль, то есть скоростью графической подсистемы. А с сетевыми задержками компьютер работает медленнее, чем человек. Это идеал производительности человека.

Если подняться на более высокий уровень абстракций, то горячие клавиши в графических интерфейсах это в каком-то смысле дальний потомок консоли, некое историческое эхо. С горячими клавишами, однострочниками и алиасами в баше человек становится быстрее компьютера приходится делать паузы, чтобы дождаться результата выполнения команд. CPU загружен практически на 100%.

Это фундаментальное преимущество клавиатуры как инструмента для ввода команд по сравнению с любыми манипуляторами. В этом же и сила консоли.

Конечно, не все команды вызываются хоткеями. Иногда требуется переместить графический объект на экране Тогда нужен курсор. Но если хоткей существует, он без вариантов быстрее, чем аналогичное действие мышью в меню. Горячие клавиши живое доказательство, что команды с клавиатуры запускаются быстрее.

В общем, из этого факта может родиться предположение, что отцы-основатели Unix всё-таки были правы, а их наследие живёт во всех операционных системах. Графическая оболочка просто тонкий слой абстракции поверх мощного фундамента, который они построили. Ведь мы помним, что macOS тоже основана на Unix и относится к семейству *nix-систем.

Ну а окошки и другие элементы графического интерфейса Windows, по мнению Apple, это вторичный продукт, скопированный с интерфейса Lisa (см. судебный процесс Apple против Microsoft c 1988 по 1994 гг).

Суд отклонил иск Apple к Microsoft. Но некоторые вещи обращают на себя внимание. Например, команда open . в консоли macOS открывает Finder в текущей директории. В Windows то же самое делает команда start . (Finder здесь называется Explorer). Окна в macOS закрываются крестиком в левом верхнем углу, а в Windows в правом углу. Возможно, на примере таких деталей Билл Гейтс доказал суду, что у него оригинальный графический интерфейс, который сильно отличается от macOS.

Скопирован графический интерфейс или нет, формально неизвестно. Но факт в том, что это просто тонкая оболочка над системой. Так же как и мышка удобный, но совсем не обязательный аксессуар.



На правах рекламы


Наша компания предлагает аренду VPS для совершенно любых проектов. Создайте собственный тарифный план в пару кликов, максимальная конфигурация позволит разместить практически любой проект 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe!

Присоединяйтесь к нашему чату в Telegram.

Подробнее..

Генерация типизированных ссылок на элементы управления Avalonia с атрибутом xName с помощью C SourceGenerator

29.11.2020 20:05:10 | Автор: admin


В апреле 2020-го года разработчиками платформы .NET 5 был анонсирован новый способ генерации исходного кода на языке программирования C# с помощью реализации интерфейса ISourceGenerator. Данный способ позволяет разработчикам анализировать пользовательский код и создавать новые исходные файлы на этапе компиляции. При этом, API новых генераторов исходного кода схож с API анализаторов Roslyn. Генерировать код можно как с помощью Roslyn Compiler API, так и методом конкатенации обычных строк.


Постановка задачи


С помощью новых генераторов исходного кода может получиться элегантно решить широкий спектр задач, включая генерацию шаблонного кода, который было бы не очень интересно и совсем не продуктивно писать вручную. Например, в приложениях, использующих AvaloniaUI фреймворк для разработки кроссплатформенных приложений с графическим интерфейсом, о котором недавно вышла статья на Хабре нередко приходится писать следующий код для получения ссылки на элемент управления, объявленный в XAML:


TextBox PasswordTextBox => this.FindControl<TextBox>("PasswordTextBox");

Элемент типа TextBox с именем PasswordTextBox при этом объявлен в XAML следующим образом:


<TextBox x:Name="PasswordTextBox"         Watermark="Please, enter your password..."         UseFloatingWatermark="True"         PasswordChar="*" />

Получать ссылку на элемент управления в XAML может понадобиться в случае необходимости применения анимаций, программного изменения стилей и свойств элемента управления, или использования кроссплатформенных типизированных привязок данных ReactiveUI, таких, как Bind, BindCommand, BindValidation, позволяющих связывать компоненты View и ViewModel без использования синтаксиса {Binding} в XAML-разметке.


public class SignUpView : ReactiveWindow<SignUpViewModel>{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        // Привязки данных ReactiveUI и ReactiveUI.Validation.        // Можно было бы схожим образом использовать расширение разметки Binding,        // но некоторые разработчики предпочитают описывать биндинги в C#.        // Почему бы не облегчить им (и многим другим) жизнь?        //        this.Bind(ViewModel, x => x.Username, x => x.UserNameTextBox.Text);        this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text);        this.BindValidation(ViewModel, x => x.CompoundValidation.Text);    }    // Шаблонный код для типизированного доступа к именованным    // элементам управления, объявленным в XAML.    private TextBox UserNameTextBox => this.FindControl<TextBox>("UserNameTextBox");    private TextBox PasswordTextBox => this.FindControl<TextBox>("PasswordTextBox");    private TextBlock CompoundValidation => this.FindControl<TextBlock>("CompoundValidation");}

Код геттеров свойств, позволяющих получить типизированный доступ к элементам управления из XAML-файла, соответствующего SignUpView, выглядит шаблонным. Было бы неплохо научиться генерировать этот код, чтобы, с одной стороны, избежать многословности, а с другой стороны чтобы избежать возможных ошибок и опечаток в имени элемента управления, объявленного в XAML, или в имени его типа.


Если мы будем всё время писать код, как в примере выше, вручную, нам придётся самостоятельно следить за изменениями имён и типов в XAML-файле, и в случае ошибки мы узнаем о том, что что-то пошло не так, только после запуска приложения. Если бы мы генерировали ссылки некоторым способом, об ошибках и опечатках мы бы узнавали уже на этапе компиляции, и тратили бы меньше времени на отладку нашего кроссплатформенного приложения (как, впрочем, и на написание таких геттеров).


Пример входных и выходных данных


Мы ожидаем, что на вход наш генератор исходного кода будет получать два файла. Для компонента представления с именем SignUpView, данными файлами будут являться XAML-разметка SignUpView.xaml, и code-behind файл SignUpView.xaml.cs, содержащий логику пользовательского интерфейса. Например, для файла разметки пользовательского интерфейса SignUpView.xaml:


<Window xmlns="http://personeltest.ru/aways/github.com/avaloniaui"        xmlns:x="http://personeltest.ru/away/schemas.microsoft.com/winfx/2006/xaml"        x:Class="Avalonia.NameGenerator.Sandbox.Views.SignUpView">    <StackPanel>        <TextBox x:Name="UserNameTextBox"                 Watermark="Please, enter user name..."                 UseFloatingWatermark="True" />        <TextBlock Name="UserNameValidation"                   Foreground="Red"                   FontSize="12" />    </StackPanel></Window>

Содержимое файла SignUpView.xaml.cs будет выглядеть следующим образом:


public partial class SignUpView : Window{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        // Мы хотим иметь доступ к типизированным элементам управления вот здесь,        // чтобы, например, писать код наподобие вот такого:        // UserNameTextBox.Text = "Violet Evergarden";        // UserNameValidation.Text = "An optional validation error message";    }}

А сгенерированное содержимое SignUpView.xaml.cs должно будет выглядеть следующим образом:


partial class SignUpView{    internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox");    internal global::Avalonia.Controls.TextBlock UserNameValidation => this.FindControl<global::Avalonia.Controls.TextBlock>("UserNameValidation");}

Префиксы global:: здесь нужны для избежания коллизий пространств имён. Дополнительно, необходимо полностью указывать имена типов также для избежания коллизий. По аналогии с WPF, мы маркируем генерируемые свойства как internal. В случае использования partial-классов базовый класс можно указывать только в одной из частей partial-класса, поэтому в сгенерированном коде мы опускаем указание базового класса таким образом пользователи нашего генератора смогут наследоваться от какого угодно наследника Window, будь то ReactiveWindow<TViewModel>, или другой тип окна.


Следует заметить, что при вызове метода FindControl обход дерева элементов производиться не будет Avalonia хранит именованные ссылки на элементы управления в словарях, называемых INameScope в терминологии Avalonia. При желании, Вы можете изучить исходный код методов FindControl и FindNameScope на GitHub.


Реализуем ISourceGenerator


Простейший генератор исходного кода, который ничего не делает, выглядит следующим образом:


[Generator]public class EmptyGenerator : ISourceGenerator{    public void Initialize(GeneratorInitializationContext context) { }    public void Execute(GeneratorExecutionContext context) { }}

В методе Initialize предлагается проинициализировать новый генератор исходного кода, а в методе Execute выполнить все важные вычисления, и при необходимости добавить сгенерированные файлы исходного кода в контекст выполнения с помощью вызова метода context.AddSource(fileName, sourceText). Давайте, для начала, добавим в сборку проекта, ссылающегося на генератор, некоторый атрибут, с помощью которого пользователи нашего генератора будут помечать классы, для которых необходимо генерировать типизированные ссылки на элементы управления Avalonia, объявленные в XAML. Изменим код нашего генератора следующим образом:


[Generator]public class NameReferenceGenerator : ISourceGenerator{    private const string AttributeName = "GenerateTypedNameReferencesAttribute";    private const string AttributeFile = "GenerateTypedNameReferencesAttribute";    private const string AttributeCode = @"// <auto-generated />using System;[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]internal sealed class GenerateTypedNameReferencesAttribute : Attribute { }";    public void Initialize(GeneratorInitializationContext context) { }    public void Execute(GeneratorExecutionContext context)    {        // Добавим код атрибута в файл 'GenerateTypedNameReferencesAttribute.cs' проекта        // разработчика, который решит воспользоваться нашим ISourceGenerator.        context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));    }}

Пока ничего сложного мы объявили исходный код атрибута, имя файла, и имя атрибута как константы, с помощью вызова SourceText.From(code) обернули строку в исходный текст, и затем добавили новый исходный файл в проект с помощью вызова context.AddSource(fileName, sourceText). Теперь в проекте, который ссылается на наш генератор, мы можем помечать интересующие нас классы с помощью атрибута [GenerateTypedNameReferences]. Для классов, помеченных данным атрибутом, мы будем генерировать типизированные ссылки на именованные элементы управления, объявленные в XAML. В случае рассматриваемого примера с SignUpView.xaml, code-behind данного файла разметки должен будет выглядеть вот так:


[GenerateTypedNameReferences]public partial class SignUpView : Window{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        // Мы пока только собираемся генерировать именованные ссылки.        // Если раскомментировать код ниже, проект не скомпилируется (пока).        // UserNameTextBox.Text = "Violet Evergarden";        // UserNameValidation.Text = "An optional validation error message";    }}

Нам необходимо научить наш ISourceGenerator следующим вещам:


  1. Находить все классы, помеченные атрибутом [GenerateTypedNameReferences];
  2. Находить соответствующие классам XAML-файлы;
  3. Извлекать полные имена типов элементов интерфейса, объявленных в XAML-файлах;
  4. Вытаскивать из XAML-файлов имена (значения Name или x:Name) элементов управления;
  5. Генерировать partial-класс и заполнять его типизированными ссылками.

Находим классы, маркированные атрибутом


Для реализации такой функциональности API генераторов исходного кода предлагает реализовать и зарегистрировать интерфейс ISyntaxReceiver, который позволит собрать все ссылки на интересующий синтаксис в одном месте. Реализуем ISyntaxReceiver, который будет собирать все ссылки на объявления классов сборки пользователя нашего генератора:


internal class NameReferenceSyntaxReceiver : ISyntaxReceiver{    public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)    {        if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&            classDeclarationSyntax.AttributeLists.Count > 0)            CandidateClasses.Add(classDeclarationSyntax);    }}

Зарегистрируем данный класс в методе ISourceGenerator.Initialize(GeneratorInitializationContext context):


context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());

Теперь, во время выполнения нашего генератора, мы можем получить доступ ко всем ClassDeclarationSyntax в сборке, и в цикле найти все классы, маркированные необходимым образом:


// Добавим в CSharpCompilation исходник нашего атрибута.var options = (CSharpParseOptions)existingCompilation.SyntaxTrees[0].Options;var compilation = existingCompilation.AddSyntaxTrees(CSharpSyntaxTree    .ParseText(SourceText.From(AttributeCode, Encoding.UTF8), options));var attributeSymbol = compilation.GetTypeByMetadataName(AttributeName);var symbols = new List<INamedTypeSymbol>();foreach (var candidateClass in nameReferenceSyntaxReceiver.CandidateClasses){    // Извлечём INamedTypeSymbol из нашего класса-кандидата.    var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);    var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);    // Проверим, маркирован ли класс с помощью нашего атрибута.    var relevantAttribute = typeSymbol!        .GetAttributes()        .FirstOrDefault(attr => attr.AttributeClass!.Equals(            attributeSymbol, SymbolEqualityComparer.Default));    if (relevantAttribute == null) {        continue;    }    // Проверим, маркирован ли класс как 'partial'.    var isPartial = candidateClass        .Modifiers        .Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword));    // Таким образом, список 'symbols' будет содержать только те    // классы, которые маркированы с помощью ключевого слова 'partial'    // и атрибута 'GenerateTypedNameReferences'.    if (isPartial) {        symbols.Add(typeSymbol);    }}

Находим подходящие XAML-файлы


В Avalonia действуют соглашения именования XAML-файлов и code-behind файлов для них. Для файла с разметкой с именем SignUpView.xaml файл code-behind будет называться SignUpView.xaml.cs, а класс внутри него, как правило, называется SignUpView. В нашей реализации генератора типизированных ссылок будем полагаться на данную схему именования. Файлы разметки Avalonia на момент реализации генератора и написания данного материала могли иметь расширения .xaml или .axaml, поэтому код, определяющий имя XAML-файла на основании имени типа будет иметь следующий вид:


var xamlFileName = $"{typeSymbol.Name}.xaml";var aXamlFileName = $"{typeSymbol.Name}.axaml";var relevantXamlFile = context    .AdditionalFiles    .FirstOrDefault(text =>         text.Path.EndsWith(xamlFileName) ||         text.Path.EndsWith(aXamlFileName));

Здесь, typeSymbol имеет тип INamedTypeSymbol и может быть получен в результате обхода списка symbols, который мы сформировали на предыдущем этапе. А ещё здесь есть один нюанс. Чтобы файлы разметки были доступны как AdditionalFiles, пользователю генератора необходимо их дополнительно включить в проект с использованием директивы MSBuild <AdditionalFiles />. Таким образом, пользователь генератора должен отредактировать файл проекта .csproj, и добавить туда вот такой <ItemGroup />:


<ItemGroup>    <!-- Очень важная директива, без которой генераторы исходного         кода не смогут выпотрошить файлы разметки! -->    <AdditionalFiles Include="**\*.xaml" /></ItemGroup>

Подробное описание <AdditionalFiles /> можно найти в материале New C# Source Generator Samples.


Извлекаем полные имена типов из XAML


Этот этап является самым сложным, но в то же время наиболее интересным. Дело в том, что нельзя просто взять и получить информацию о пространстве имён, в котором находится элемент управления, объявленный в XAML-разметке. А нам, из-за нашего желания избежать коллизий и генерировать вменяемый код, который всегда будет компилироваться и работать, позарез нужно уметь получать полную квалификацию пространства имён, в котором находится тип.


Хорошая новость заключается в том, что фреймворк AvaloniaUI использует новый компилятор XamlX, целиком написанный @kekekeks. Этот компилятор мало того, что не имеет рантайм-зависимостей, умеет находить ошибки в XAML на этапе компиляции, работает намного быстрее загрузчиков XAML из WPF, UWP, XF и других технологий, так ещё и предоставляет нам удобный API для парсинга XAML и разрешения типов. Таким образом, мы можем позволить себе подключить XamlX в проект исходниками (git submodule add ://repo ./path), и написать свой собственный MiniCompiler, который наш генератор исходного кода будет вызывать для компиляции XAML и получения полной информации о типах, даже если они лежат в каких-нибудь сторонних сборках. Реализация XamlX.XamlCompiler в виде нашего маленького MiniCompiler, который мы собираемся натравливать на XAML-файлы, имеет вид:


internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>{    public static MiniCompiler CreateDefault(RoslynTypeSystem typeSystem, params string[] additionalTypes)    {        var mappings = new XamlLanguageTypeMappings(typeSystem);        foreach (var additionalType in additionalTypes)            mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));        var configuration = new TransformerConfiguration(            typeSystem,            typeSystem.Assemblies[0],            mappings);        return new MiniCompiler(configuration);    }    private MiniCompiler(TransformerConfiguration configuration)        : base(configuration, new XamlLanguageEmitMappings<object, IXamlEmitResult>(), false)    {        Transformers.Add(new NameDirectiveTransformer());        Transformers.Add(new DataTemplateTransformer());        Transformers.Add(new KnownDirectivesTransformer());        Transformers.Add(new XamlIntrinsicsTransformer());        Transformers.Add(new XArgumentsTransformer());        Transformers.Add(new TypeReferenceResolver());    }    protected override XamlEmitContext<object, IXamlEmitResult> InitCodeGen(        IFileSource file,        Func<string, IXamlType, IXamlTypeBuilder<object>> createSubType,        object codeGen, XamlRuntimeContext<object, IXamlEmitResult> context,        bool needContextLocal) =>        throw new NotSupportedException();}

В нашем MiniCompiler мы используем дефолтные трансформеры XamlX и один особенный NameDirectiveTransformer, тоже написанный @kekekeks, который умеет преобразовывать XAML-атрибут x:Name в XAML-атрибут Name для того, чтобы впоследствии обходить полученное AST и вытаскивать имена элементов управления было проще. Такой NameDirectiveTransformer выглядит следующим образом:


internal class NameDirectiveTransformer : IXamlAstTransformer{    public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)    {        // Нас интересуют только объекты.        if (node is XamlAstObjectNode objectNode)        {            for (var index = 0; index < objectNode.Children.Count; index++)            {                // Если мы встретили x:Name, заменяем его на Name и                 // продолжаем обходить потомков XamlAstObjectNode дальше.                var child = objectNode.Children[index];                if (child is XamlAstXmlDirective directive &&                    directive.Namespace == XamlNamespaces.Xaml2006 &&                    directive.Name == "Name")                    objectNode.Children[index] = new XamlAstXamlPropertyValueNode(                        directive,                        new XamlAstNamePropertyReference(                            directive, objectNode.Type, "Name", objectNode.Type),                        directive.Values);            }        }        return node;    }}

Фабрика MiniCompiler.CreateDefault принимает первым аргументом любопытный тип RoslynTypeSystem, который вы не найдёте в исходниках XamlX. Данный тип реализует интерфейс IXamlTypeSystem, а это значит, что всё самое сложное только начинается. Чтобы наш маленький компилятор заработал внутри нашего генератора исходного кода, нам необходимо реализовать систему типов XamlX поверх API семантической модели компилятора Roslyn. Для реализации новой IXamlTypeSystem пришлось реализовывать много-много интерфейсов (IXamlType для классов, IXamlAssembly для сборок, IXamlMethod для методов, IXamlProperty для свойств и др). Реализация IXamlAssembly, например, выглядит вот так:


public class RoslynAssembly : IXamlAssembly{    private readonly IAssemblySymbol _symbol;    public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol;    public bool Equals(IXamlAssembly other) =>        other is RoslynAssembly roslynAssembly &&        SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol);    public string Name => _symbol.Name;    public IReadOnlyList<IXamlCustomAttribute> CustomAttributes =>        _symbol.GetAttributes()            .Select(data => new RoslynAttribute(data, this))            .ToList();    public IXamlType FindType(string fullName)    {        var type = _symbol.GetTypeByMetadataName(fullName);        return type is null ? null : new RoslynType(type, this);    }}

После реализации всех необходимых интерфейсов мы наконец сможем распарсить XAML инструментами XamlX, создать инстанс нашей реализации RoslynTypeSystem, передав ей в конструктор CSharpCompilation, которую мы уже извлекли из контекста генерации на предыдущем этапе, и трансформировать полученное в результате парсинга AST в AST с включённой информацией о пространствах имён и типах:


var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>());MiniCompiler.CreateDefault(    new RoslynTypeSystem(compilation), // 'compilation' имеет тип 'CSharpCompilation'    "Avalonia.Metadata.XmlnsDefinitionAttribute")    .Transform(parsed);

Готово! Осталось извлечь все именованные объекты из дерева и дело в шляпе.


Находим именованные объекты XAML


На предыдущем этапе мы уже рассмотрели трансформер AST XamlX, реализующий IXamlAstTransformer, а теперь давайте рассмотрим и напишем посетителя узлов этого AST, реализующий интерфейс IXamlAstVisitor. Наш посетитель будет выглядеть следующим образом:


internal sealed class NameReceiver : IXamlAstVisitor{    private readonly List<(string TypeName, string Name)> _items =        new List<(string TypeName, string Name)>();    public IReadOnlyList<(string TypeName, string Name)> Controls => _items;    public IXamlAstNode Visit(IXamlAstNode node)    {        if (node is XamlAstObjectNode objectNode)        {            // Извлекаем тип AST-узла. Данный тип нам вывел XamlX в            // процессе взаимодействия с нашей RoslynTypeSystem.            //            var clrType = objectNode.Type.GetClrType();            foreach (var child in objectNode.Children)            {                // Если мы в результате обхода потомков встретили свойство,                // которое называется 'Name', и при этом внутри 'Name' лежит строка,                // то добавляем в список элементов '_items' имя и CLR-тип элемента AST.                //                if (child is XamlAstXamlPropertyValueNode propertyValueNode &&                    propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&                    namedProperty.Name == "Name" &&                    propertyValueNode.Values.Count > 0 &&                    propertyValueNode.Values[0] is XamlAstTextNode text)                {                    var typeNamePair = ($@"{clrType.Namespace}.{clrType.Name}", text.Text);                    if (!_items.Contains(typeNamePair))                        _items.Add(typeNamePair);                }            }            return node;        }        return node;    }    public void Push(IXamlAstNode node) { }    public void Pop() { }}

Процесс парсинга XAML и извлечения типов и имён XAML-элементов теперь выглядит так:


var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>());MiniCompiler.CreateDefault(    new RoslynTypeSystem(compilation), // 'compilation' имеет тип 'CSharpCompilation'    "Avalonia.Metadata.XmlnsDefinitionAttribute")    .Transform(parsed);var visitor = new NameReceiver();parsed.Root.Visit(visitor);parsed.Root.VisitChildren(visitor);// Теперь у нас есть и типы, и имена элементов.var controls = visitor.Controls;

Генерируем типизированные ссылки


Наконец, можно перейти к заключительному этапу разработки нашего генератора исходного кода. У нас есть всё, что было нужно для полного счастья и типы, и пространства имён, и имена элементов. А это значит, что нам необходимо сгенерировать partial-класс, сложив туда ссылки на все найденные именованные элементы пользовательского интерфейса, объявленные в XAML. Метод, генерирующий такой partial-класс, будет иметь вид:


private static string GenerateSourceCode(    List<(string TypeName, string Name)> controls, // Список имён и типов с предыдущего этапа.    INamedTypeSymbol classSymbol, // Элемент из списка 'symbols', сформированного в самом начале.    AdditionalText xamlFile){    var className = classSymbol.Name;    var nameSpace = classSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat);    var namedControls = controls        .Select(info => "        " +                       $"internal global::{info.TypeName} {info.Name} => " +                       $"this.FindControl<global::{info.TypeName}>(\"{info.Name}\");");    return $@"// <auto-generated />using Avalonia.Controls;namespace {nameSpace}{{    partial class {className}    {{{string.Join("\n", namedControls)}       }}}}";}

Добавим полученный код в контекст выполнения GeneratorExecutionContext:


var sourceCode = GenerateSourceCode(controls, symbol, relevantXamlFile);context.AddSource($"{symbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));

Готово!


Результат


Инструментарий Visual Studio понимает, что при изменении XAML-файла, включённого в проект как <AdditionalFile />, необходимо вызвать генератор исходного кода ещё раз, и обновить сгенерированные исходники. Таким образом, при редактировании XAML-файлов, ссылки на новые элементы управления, добавляемые в XAML в процессе разработки, будут автоматически становиться доступными из C#-файла с расширением .xaml.cs.


ezgif-1-f52e7303c26f


Исходный код генератора доступен на GitHub.


Интеграция генераторов исходного кода с JetBrains Rider и ReSharper доступна в последних EAP, что позволяет утверждать, что реализованная технология является кроссплатформенной, и будет работать на Windows, Linux, и macOS. В дальнейшем мы собираемся заинтегрировать получившийся генератор в Avalonia, чтобы в новых версиях фреймворка генерация типизированных ссылок стала доступна из коробки.


А вот так выглядит обновлённый пример кода из самого начала статьи, с биндингами и ReactiveUI.Validation:


[GenerateTypedNameReferences]public class SignUpView : ReactiveWindow<SignUpViewModel>{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        this.Bind(ViewModel, x => x.Username, x => x.UserNameTextBox.Text);        this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text);        this.BindValidation(ViewModel, x => x.CompoundValidation.Text);    }}

Ссылки


Подробнее..

Генерация типизированных ссылок на элементы управления AvaloniaUI с атрибутом xName с помощью C Source Generators API

29.11.2020 22:13:53 | Автор: admin


В апреле 2020-го года разработчиками платформы .NET 5 был анонсирован новый способ генерации исходного кода на языке программирования C# с помощью реализации интерфейса ISourceGenerator. Данный способ позволяет разработчикам анализировать пользовательский код и создавать новые исходные файлы на этапе компиляции. При этом, API новых генераторов исходного кода схож с API анализаторов Roslyn. Генерировать код можно как с помощью Roslyn Compiler API, так и методом конкатенации обычных строк.


Постановка задачи


С помощью новых генераторов исходного кода может получиться элегантно решить широкий спектр задач, включая генерацию шаблонного кода, который было бы не очень интересно и совсем не продуктивно писать вручную. Например, в приложениях, использующих AvaloniaUI фреймворк для разработки кроссплатформенных приложений с графическим интерфейсом, о котором недавно вышла статья на Хабре нередко приходится писать следующий код для получения ссылки на элемент управления, объявленный в XAML:


TextBox PasswordTextBox => this.FindControl<TextBox>("PasswordTextBox");

Элемент типа TextBox с именем PasswordTextBox при этом объявлен в XAML следующим образом:


<TextBox x:Name="PasswordTextBox"         Watermark="Please, enter your password..."         UseFloatingWatermark="True"         PasswordChar="*" />

Получать ссылку на элемент управления в XAML может понадобиться в случае необходимости применения анимаций, программного изменения стилей и свойств элемента управления, или использования кроссплатформенных типизированных привязок данных ReactiveUI, таких, как Bind, BindCommand, BindValidation, позволяющих связывать компоненты View и ViewModel без использования синтаксиса {Binding} в XAML-разметке.


public class SignUpView : ReactiveWindow<SignUpViewModel>{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        // Привязки данных ReactiveUI и ReactiveUI.Validation.        // Можно было бы схожим образом использовать расширение разметки Binding,        // но некоторые разработчики предпочитают описывать биндинги в C#.        // Почему бы не облегчить им (и многим другим) жизнь?        //        this.Bind(ViewModel, x => x.Username, x => x.UserNameTextBox.Text);        this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text);        this.BindValidation(ViewModel, x => x.CompoundValidation.Text);    }    // Шаблонный код для типизированного доступа к именованным    // элементам управления, объявленным в XAML.    TextBox UserNameTextBox => this.FindControl<TextBox>("UserNameTextBox");    TextBox PasswordTextBox => this.FindControl<TextBox>("PasswordTextBox");    TextBlock CompoundValidation => this.FindControl<TextBlock>("CompoundValidation");}

Код геттеров свойств, позволяющих получить типизированный доступ к элементам управления из XAML-файла, соответствующего SignUpView, выглядит шаблонным. Было бы неплохо научиться генерировать этот код, чтобы, с одной стороны, избежать многословности, а с другой стороны чтобы избежать возможных ошибок и опечаток в имени элемента управления, объявленного в XAML, или в имени его типа.


Если мы будем всё время писать код, как в примере выше, вручную, нам придётся самостоятельно следить за изменениями имён и типов в XAML-файле, и в случае ошибки мы узнаем о том, что что-то пошло не так, только после запуска приложения. Если бы мы генерировали ссылки некоторым способом, об ошибках и опечатках мы бы узнавали уже на этапе компиляции, и тратили бы меньше времени на отладку нашего кроссплатформенного приложения (как, впрочем, и на написание таких геттеров).


Пример входных и выходных данных


Мы ожидаем, что на вход наш генератор исходного кода будет получать два файла. Для компонента представления с именем SignUpView, данными файлами будут являться XAML-разметка SignUpView.xaml, и code-behind файл SignUpView.xaml.cs, содержащий логику пользовательского интерфейса. Например, для файла разметки пользовательского интерфейса SignUpView.xaml:


<Window xmlns="http://personeltest.ru/aways/github.com/avaloniaui"        xmlns:x="http://personeltest.ru/away/schemas.microsoft.com/winfx/2006/xaml"        x:Class="Avalonia.NameGenerator.Sandbox.Views.SignUpView">    <StackPanel>        <TextBox x:Name="UserNameTextBox"                 Watermark="Please, enter user name..."                 UseFloatingWatermark="True" />        <TextBlock Name="UserNameValidation"                   Foreground="Red"                   FontSize="12" />    </StackPanel></Window>

Содержимое файла SignUpView.xaml.cs будет выглядеть следующим образом:


public partial class SignUpView : Window{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        // Мы хотим иметь доступ к типизированным элементам управления вот здесь,        // чтобы, например, писать код наподобие вот такого:        // UserNameTextBox.Text = "Violet Evergarden";        // UserNameValidation.Text = "An optional validation error message";    }}

А сгенерированное содержимое SignUpView.xaml.cs должно будет выглядеть следующим образом:


partial class SignUpView{    internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox");    internal global::Avalonia.Controls.TextBlock UserNameValidation => this.FindControl<global::Avalonia.Controls.TextBlock>("UserNameValidation");}

Префиксы global:: здесь нужны для избежания коллизий пространств имён. Дополнительно, необходимо полностью указывать имена типов также для избежания коллизий. По аналогии с WPF, мы маркируем генерируемые свойства как internal. В случае использования partial-классов базовый класс можно указывать только в одной из частей partial-класса, поэтому в сгенерированном коде мы опускаем указание базового класса таким образом пользователи нашего генератора смогут наследоваться от какого угодно наследника Window, будь то ReactiveWindow<TViewModel>, или другой тип окна.


Следует заметить, что при вызове метода FindControl обход дерева элементов производиться не будет Avalonia хранит именованные ссылки на элементы управления в словарях, называемых INameScope в терминологии Avalonia. При желании, Вы можете изучить исходный код методов FindControl и FindNameScope на GitHub.


Реализуем интерфейс ISourceGenerator


Простейший генератор исходного кода, который ничего не делает, выглядит следующим образом:


[Generator]public class EmptyGenerator : ISourceGenerator{    public void Initialize(GeneratorInitializationContext context) { }    public void Execute(GeneratorExecutionContext context) { }}

В методе Initialize предлагается проинициализировать новый генератор исходного кода, а в методе Execute выполнить все важные вычисления, и при необходимости добавить сгенерированные файлы исходного кода в контекст выполнения с помощью вызова метода context.AddSource(fileName, sourceText). При этом, файл проекта генератора исходного кода выглядит следующим образом:


<Project Sdk="Microsoft.NET.Sdk">    <PropertyGroup>        <TargetFramework>netstandard2.0</TargetFramework>        <LangVersion>preview</LangVersion>        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>        <IncludeBuildOutput>false</IncludeBuildOutput>    </PropertyGroup>    <ItemGroup>        <PackageReference            Include="Microsoft.CodeAnalysis.CSharp"            Version="3.8.0-5.final"            PrivateAssets="all" />        <PackageReference            Include="Microsoft.CodeAnalysis.Analyzers"            Version="3.3.1"            PrivateAssets="all" />    </ItemGroup>    <ItemGroup>        <None Include="$(OutputPath)\$(AssemblyName).dll"              Pack="true"              PackagePath="analyzers/dotnet/cs"              Visible="false" />    </ItemGroup></Project>

Давайте, для начала, добавим в сборку проекта, ссылающегося на генератор, некоторый атрибут, с помощью которого пользователи нашего генератора будут помечать классы, для которых необходимо генерировать типизированные ссылки на элементы управления Avalonia, объявленные в XAML. Изменим код нашего генератора следующим образом:


[Generator]public class NameReferenceGenerator : ISourceGenerator{    private const string AttributeName = "GenerateTypedNameReferencesAttribute";    private const string AttributeFile = "GenerateTypedNameReferencesAttribute";    private const string AttributeCode = @"// <auto-generated />using System;[AttributeUsage(AttributeTargets.Class, Inherited=false, AllowMultiple=false)]internal sealed class GenerateTypedNameReferencesAttribute : Attribute { }";    public void Initialize(GeneratorInitializationContext context) { }    public void Execute(GeneratorExecutionContext context)    {        // Добавим код атрибута в файл 'GenerateTypedNameReferencesAttribute.cs'         // проекта разработчика, который решит воспользоваться нашим генератором.        context.AddSource(AttributeFile,            SourceText.From(                AttributeCode, Encoding.UTF8));    }}

Пока ничего сложного мы объявили исходный код атрибута, имя файла, и имя атрибута как константы, с помощью вызова SourceText.From(code) обернули строку в исходный текст, и затем добавили новый исходный файл в проект с помощью вызова context.AddSource(fileName, sourceText). Теперь в проекте, который ссылается на наш генератор, мы можем помечать интересующие нас классы с помощью атрибута [GenerateTypedNameReferences]. Для классов, помеченных данным атрибутом, мы будем генерировать типизированные ссылки на именованные элементы управления, объявленные в XAML. В случае рассматриваемого примера с SignUpView.xaml, code-behind данного файла разметки должен будет выглядеть вот так:


[GenerateTypedNameReferences]public partial class SignUpView : Window{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        // Мы пока только собираемся генерировать именованные ссылки.        // Если раскомментировать код ниже, проект не скомпилируется (пока).        // UserNameTextBox.Text = "Violet Evergarden";        // UserNameValidation.Text = "An optional validation error message";    }}

Нам необходимо научить наш ISourceGenerator следующим вещам:


  1. Находить все классы, помеченные атрибутом [GenerateTypedNameReferences];
  2. Находить соответствующие классам XAML-файлы;
  3. Извлекать полные имена типов элементов интерфейса, объявленных в XAML-файлах;
  4. Вытаскивать из XAML-файлов имена (значения Name или x:Name) элементов управления;
  5. Генерировать partial-класс и заполнять его типизированными ссылками.

Находим классы, маркированные атрибутом


Для реализации такой функциональности API генераторов исходного кода предлагает реализовать и зарегистрировать интерфейс ISyntaxReceiver, который позволит собрать все ссылки на интересующий синтаксис в одном месте. Реализуем ISyntaxReceiver, который будет собирать все ссылки на объявления классов сборки пользователя нашего генератора:


internal class NameReferenceSyntaxReceiver : ISyntaxReceiver{    public List<ClassDeclarationSyntax> CandidateClasses { get; } =        new List<ClassDeclarationSyntax>();    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)    {        if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&            classDeclarationSyntax.AttributeLists.Count > 0)            CandidateClasses.Add(classDeclarationSyntax);    }}

Зарегистрируем данный класс в методе ISourceGenerator.Initialize(GeneratorInitializationContext context):


context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());

Теперь, во время выполнения нашего генератора, мы можем получить доступ ко всем ClassDeclarationSyntax в сборке, и в цикле найти все классы, маркированные необходимым образом:


// Добавим в CSharpCompilation исходник нашего атрибута.var options = (CSharpParseOptions)existingCompilation.SyntaxTrees[0].Options;var compilation = existingCompilation.AddSyntaxTrees(CSharpSyntaxTree    .ParseText(SourceText.From(AttributeCode, Encoding.UTF8), options));var attributeSymbol = compilation.GetTypeByMetadataName(AttributeName);var symbols = new List<INamedTypeSymbol>();foreach (var candidateClass in nameReferenceSyntaxReceiver.CandidateClasses){    // Извлечём INamedTypeSymbol из нашего класса-кандидата.    var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);    var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);    // Проверим, маркирован ли класс с помощью нашего атрибута.    var relevantAttribute = typeSymbol!        .GetAttributes()        .FirstOrDefault(attr => attr.AttributeClass!.Equals(            attributeSymbol, SymbolEqualityComparer.Default));    if (relevantAttribute == null) {        continue;    }    // Проверим, маркирован ли класс как 'partial'.    var isPartial = candidateClass        .Modifiers        .Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword));    // Таким образом, список 'symbols' будет содержать только те    // классы, которые маркированы с помощью ключевого слова 'partial'    // и атрибута 'GenerateTypedNameReferences'.    if (isPartial) {        symbols.Add(typeSymbol);    }}

Находим подходящие XAML-файлы


В Avalonia действуют соглашения именования XAML-файлов и code-behind файлов для них. Для файла с разметкой с именем SignUpView.xaml файл code-behind будет называться SignUpView.xaml.cs, а класс внутри него, как правило, называется SignUpView. В нашей реализации генератора типизированных ссылок будем полагаться на данную схему именования. Файлы разметки Avalonia на момент реализации генератора и написания данного материала могли иметь расширения .xaml или .axaml, поэтому код, определяющий имя XAML-файла на основании имени типа будет иметь следующий вид:


var xamlFileName = $"{typeSymbol.Name}.xaml";var aXamlFileName = $"{typeSymbol.Name}.axaml";var relevantXamlFile = context    .AdditionalFiles    .FirstOrDefault(text =>         text.Path.EndsWith(xamlFileName) ||         text.Path.EndsWith(aXamlFileName));

Здесь, typeSymbol имеет тип INamedTypeSymbol и может быть получен в результате обхода списка symbols, который мы сформировали на предыдущем этапе. А ещё здесь есть один нюанс. Чтобы файлы разметки были доступны как AdditionalFiles, пользователю генератора необходимо их дополнительно включить в проект с использованием директивы MSBuild <AdditionalFiles />. Таким образом, пользователь генератора должен отредактировать файл проекта .csproj, и добавить туда вот такой <ItemGroup />:


<ItemGroup>    <!-- Очень важная директива, без которой генераторы исходного         кода не смогут выпотрошить файлы разметки! -->    <AdditionalFiles Include="**\*.xaml" /></ItemGroup>

Подробное описание <AdditionalFiles /> можно найти в материале New C# Source Generator Samples.


Извлекаем полные имена типов из XAML


Этот этап является самым сложным, но в то же время наиболее интересным. Дело в том, что нельзя просто взять и получить информацию о пространстве имён, в котором находится элемент управления, объявленный в XAML-разметке. А нам, из-за нашего желания избежать коллизий и генерировать вменяемый код, который всегда будет компилироваться и работать, позарез нужно уметь получать полную квалификацию пространства имён, в котором находится тип.


Хорошая новость заключается в том, что фреймворк AvaloniaUI использует новый компилятор XamlX, целиком написанный @kekekeks. Этот компилятор мало того, что не имеет рантайм-зависимостей, умеет находить ошибки в XAML на этапе компиляции, работает намного быстрее загрузчиков XAML из WPF, UWP, XF и других технологий, так ещё и предоставляет нам удобный API для парсинга XAML и разрешения типов. Таким образом, мы можем позволить себе подключить XamlX в проект исходниками (git submodule add ://repo ./path), и написать свой собственный MiniCompiler, который наш генератор исходного кода будет вызывать для компиляции XAML и получения полной информации о типах, даже если они лежат в каких-нибудь сторонних сборках. Реализация XamlX.XamlCompiler в виде нашего маленького MiniCompiler, который мы собираемся натравливать на XAML-файлы, имеет вид:


internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>{    public static MiniCompiler CreateDefault(        RoslynTypeSystem typeSystem,        params string[] additionalTypes)    {        var mappings = new XamlLanguageTypeMappings(typeSystem);        foreach (var additionalType in additionalTypes)            mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));        var configuration = new TransformerConfiguration(            typeSystem,            typeSystem.Assemblies[0],            mappings);        return new MiniCompiler(configuration);    }    private MiniCompiler(TransformerConfiguration configuration)        : base(configuration,               new XamlLanguageEmitMappings<object, IXamlEmitResult>(),               false)    {        Transformers.Add(new NameDirectiveTransformer());        Transformers.Add(new DataTemplateTransformer());        Transformers.Add(new KnownDirectivesTransformer());        Transformers.Add(new XamlIntrinsicsTransformer());        Transformers.Add(new XArgumentsTransformer());        Transformers.Add(new TypeReferenceResolver());    }    protected override XamlEmitContext<object, IXamlEmitResult> InitCodeGen(        IFileSource file,        Func<string, IXamlType, IXamlTypeBuilder<object>> createSubType,        object codeGen, XamlRuntimeContext<object, IXamlEmitResult> context,        bool needContextLocal) =>        throw new NotSupportedException();}

В нашем MiniCompiler мы используем дефолтные трансформеры XamlX и один особенный NameDirectiveTransformer, тоже написанный @kekekeks, который умеет преобразовывать XAML-атрибут x:Name в XAML-атрибут Name для того, чтобы впоследствии обходить полученное AST и вытаскивать имена элементов управления было проще. Такой NameDirectiveTransformer выглядит следующим образом:


internal class NameDirectiveTransformer : IXamlAstTransformer{    public IXamlAstNode Transform(        AstTransformationContext context,        IXamlAstNode node)    {        // Нас интересуют только объекты.        if (node is XamlAstObjectNode objectNode)        {            for (var index = 0; index < objectNode.Children.Count; index++)            {                // Если мы встретили x:Name, заменяем его на Name и                 // продолжаем обходить потомков XamlAstObjectNode дальше.                var child = objectNode.Children[index];                if (child is XamlAstXmlDirective directive &&                    directive.Namespace == XamlNamespaces.Xaml2006 &&                    directive.Name == "Name")                    objectNode.Children[index] =                        new XamlAstXamlPropertyValueNode(                            directive,                            new XamlAstNamePropertyReference(                                directive, objectNode.Type, "Name", objectNode.Type),                            directive.Values);            }        }        return node;    }}

Фабрика MiniCompiler.CreateDefault принимает первым аргументом любопытный тип RoslynTypeSystem, который вы не найдёте в исходниках XamlX. Данный тип реализует интерфейс IXamlTypeSystem, а это значит, что всё самое сложное только начинается. Чтобы наш маленький компилятор заработал внутри нашего генератора исходного кода, нам необходимо реализовать систему типов XamlX поверх API семантической модели компилятора Roslyn. Для реализации новой IXamlTypeSystem пришлось реализовывать много-много интерфейсов (IXamlType для классов, IXamlAssembly для сборок, IXamlMethod для методов, IXamlProperty для свойств и др). Реализация IXamlAssembly, например, выглядит вот так:


public class RoslynAssembly : IXamlAssembly{    private readonly IAssemblySymbol _symbol;    public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol;    public bool Equals(IXamlAssembly other) =>        other is RoslynAssembly roslynAssembly &&        SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol);    public string Name => _symbol.Name;    public IReadOnlyList<IXamlCustomAttribute> CustomAttributes =>        _symbol.GetAttributes()            .Select(data => new RoslynAttribute(data, this))            .ToList();    public IXamlType FindType(string fullName)    {        var type = _symbol.GetTypeByMetadataName(fullName);        return type is null ? null : new RoslynType(type, this);    }}

После реализации всех необходимых интерфейсов мы наконец сможем распарсить XAML инструментами XamlX, создать инстанс нашей реализации RoslynTypeSystem, передав ей в конструктор CSharpCompilation, которую мы уже извлекли из контекста генерации на предыдущем этапе, и трансформировать полученное в результате парсинга AST в AST с включённой информацией о пространствах имён и типах:


var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>());MiniCompiler.CreateDefault(    // 'compilation' имеет тип 'CSharpCompilation'    new RoslynTypeSystem(compilation),    "Avalonia.Metadata.XmlnsDefinitionAttribute")    .Transform(parsed);

Готово! Осталось извлечь все именованные объекты из дерева и дело в шляпе.


Находим именованные объекты XAML


На предыдущем этапе мы уже рассмотрели трансформер AST XamlX, реализующий IXamlAstTransformer, а теперь давайте рассмотрим и напишем посетителя узлов этого AST, реализующий интерфейс IXamlAstVisitor. Наш посетитель будет выглядеть следующим образом:


internal sealed class NameReceiver : IXamlAstVisitor{    private readonly List<(string TypeName, string Name)> _items =        new List<(string TypeName, string Name)>();    public IReadOnlyList<(string TypeName, string Name)> Controls => _items;    public IXamlAstNode Visit(IXamlAstNode node)    {        if (node is XamlAstObjectNode objectNode)        {            // Извлекаем тип AST-узла. Данный тип нам вывел XamlX в            // процессе взаимодействия с нашей RoslynTypeSystem.            //            var clrType = objectNode.Type.GetClrType();            foreach (var child in objectNode.Children)            {                // Если мы в результате обхода потомков встретили свойство,                // которое называется 'Name', и при этом внутри 'Name' лежит строка,                // то добавляем в список элементов '_items' имя и CLR-тип элемента AST.                //                if (child is XamlAstXamlPropertyValueNode propertyValueNode &&                    propertyValueNode.Property is XamlAstNamePropertyReference named &&                    named.Name == "Name" &&                    propertyValueNode.Values.Count > 0 &&                    propertyValueNode.Values[0] is XamlAstTextNode text)                {                    var nsType = $@"{clrType.Namespace}.{clrType.Name}";                    var typeNamePair = (nsType, text.Text);                    if (!_items.Contains(typeNamePair))                        _items.Add(typeNamePair);                }            }            return node;        }        return node;    }    public void Push(IXamlAstNode node) { }    public void Pop() { }}

Процесс парсинга XAML и извлечения типов и имён XAML-элементов теперь выглядит так:


var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>());MiniCompiler.CreateDefault(    // 'compilation' имеет тип 'CSharpCompilation'    new RoslynTypeSystem(compilation),    "Avalonia.Metadata.XmlnsDefinitionAttribute")    .Transform(parsed);var visitor = new NameReceiver();parsed.Root.Visit(visitor);parsed.Root.VisitChildren(visitor);// Теперь у нас есть и типы, и имена элементов.var controls = visitor.Controls;

Генерируем типизированные ссылки


Наконец, можно перейти к заключительному этапу разработки нашего генератора исходного кода. У нас есть всё, что было нужно для полного счастья и типы, и пространства имён, и имена элементов. А это значит, что нам необходимо сгенерировать partial-класс, сложив туда ссылки на все найденные именованные элементы пользовательского интерфейса, объявленные в XAML. Метод, генерирующий такой partial-класс, будет иметь вид:


private static string GenerateSourceCode(    List<(string TypeName, string Name)> controls,    INamedTypeSymbol classSymbol,    AdditionalText xamlFile){    var className = classSymbol.Name;    var nameSpace = classSymbol.ContainingNamespace        .ToDisplayString(SymbolDisplayFormat);    var namedControls = controls        .Select(info => "        " +                       $"internal global::{info.TypeName} {info.Name} => " +                       $"this.FindControl<global::{info.TypeName}>(\"{info.Name}\");");    return $@"// <auto-generated />using Avalonia.Controls;namespace {nameSpace}{{    partial class {className}    {{{string.Join("\n", namedControls)}       }}}}";}

Добавим полученный код в контекст выполнения GeneratorExecutionContext:


var sourceCode = GenerateSourceCode(controls, symbol, relevantXamlFile);context.AddSource($"{symbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));

Готово!


Результат


Инструментарий Visual Studio понимает, что при изменении XAML-файла, включённого в проект как <AdditionalFile />, необходимо вызвать генератор исходного кода ещё раз, и обновить сгенерированные исходники. Таким образом, при редактировании XAML-файлов, ссылки на новые элементы управления, добавляемые в XAML в процессе разработки, будут автоматически становиться доступными из C#-файла с расширением .xaml.cs.


ezgif-1-f52e7303c26f


Исходный код генератора доступен на GitHub.


Интеграция генераторов исходного кода с JetBrains Rider и ReSharper доступна в последних EAP, что позволяет утверждать, что реализованная технология является кроссплатформенной, и будет работать на Windows, Linux, и macOS. В дальнейшем мы собираемся заинтегрировать получившийся генератор в Avalonia, чтобы в новых версиях фреймворка генерация типизированных ссылок стала доступна из коробки.


А вот так выглядит обновлённый пример кода из самого начала статьи, с биндингами и ReactiveUI.Validation:


[GenerateTypedNameReferences]public class SignUpView : ReactiveWindow<SignUpViewModel>{    public SignUpView()    {        AvaloniaXamlLoader.Load(this);        this.Bind(ViewModel, x => x.Username, x => x.UserNameTextBox.Text);        this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text);        this.BindValidation(ViewModel, x => x.CompoundValidation.Text);    }}

Ссылки


Подробнее..

Создание TreeView и ComboBox в Glade

05.04.2021 16:10:13 | Автор: admin

Привет всем! У многих начинающих разработчиков, которые задумали создавать интерфейс своего очередного шедевра в Glade часто возникают трудности с конструированием различного рода списков. У меня тоже были похожие затруднения. Я решил написать данный пост, надеясь, что он поможет новичкам разобраться с созданием упомянутых элементов. Постараюсь разъяснить все максимально подробно.

TreeView

Итак, допустим, нам нужно получить что-то вроде этого:

Самое первое, что нужно сделать это открыть Glade и создать новое окно GtkWindow из вкладки окон. Далее, из соседней вкладки контейнеров выбираем GtkScrolledWindow и перемещаем на созданное окно. Должно получится вот так:

Далее, из вкладки Display перемещаем на окно так нужный нам TreeView. После перемещения справа от формы нужно найти и заполнить поле "модель TreeView":

Нажимаем на значок карандаша и видим окно:

В этом окне нужно нажать кнопку "Создать". Окно сразу закроется, а в поле модели появится надпись liststore1. После всех манипуляций в левой панели должна отображаться вот такая иерархия:

Выделяем пункт liststore1 и в правой части обращаем внимание на следующую таблицу:

Нам нужно ее заполнить. Для заполнения надо нажать на выделенную строку и выбрать в появившемся выпадающем списке необходимое значение типа столбца и дописать его название. Я сделал так:

Ниже этой таблицы есть еще одна, в которой нужно создать строки:

Я заполнил ее таким образом:

Что дальше? А дальше нам надо вызвать редактор дерева, чтобы связать полученный liststore с элементами списка. Чтобы появился редактор нужно в левой панели кликнуть правой кнопкой мыши на элементе GtkTreeView или сделать то же самое в области формы. В появившемся меню нажимаем "Edit". Должно показаться такое окно:

Нажимаем на плюс в левой части и видим примерно следующее:

Заполняем поле ID (по желанию) и дальше нажимаем правой кнопкой мыши по выделенному элементу в левой части. В появившемся меню выбираем добавление дочернего элемента "Текст" и переходим уже к нему:

Здесь очень важно в разделе параметров и атрибутов выбрать нужный нам элемент из liststore. По умолчанию там выставлено значение "не определено". Нажав на выпадающий список, выбираем "name-gchararray". Таким образом мы создали первый столбец. Для создания второго столбца нажимаем снова на плюс в левой части и повторяем все то же самое, но с другими параметрами. Для второго столбца должно получиться так:

А для третьего вот так:

После всех действий иерархия компонентов в левой панели редактора должна выглядеть таким образом:

Еще нужно переименовать столбцы, так как по умолчанию они выводятся как "column". Для переименования выделите нужный столбец в левой панели, например, column_name, а в правой части найдите это:

Вместо значения по умолчанию впишите значение "Имя". И так проделайте со всеми столбцами. Теперь можно запустить предварительный просмотр того, что мы тут наворотили. Нажмите на значок с шестеренками вверху (появляется при выделении какого-либо виджета в форме) и полюбуйтесь на свое творение. Должно получиться так же, как на самой первой картинке.

ComboBox

Для выпадающих списков логика создания примерно такая же, поэтому подробно расписывать весь процесс я не буду. Вот пример редактора выпадающего списка и как видно, слева выделен только один элемент:

В параметрах и атрибутах указан name-gchararray из list_store. Вот содержимое list_store:

По такому же типу можно создать и TreeView если вам нужен обычный список без нескольких столбцов. Разница в том, что для TreeView в редакторе дерева создается родитель GtkTreeViewColumn и его дочерний элемент GtkCellRendererText, а для выпадающего списка только GtkCellRendererText.

На этом все! Надеюсь, что понятно объяснил и помог новичкам разобраться в данной теме.

Подробнее..

Перевод Kubevious революционная панель управления Kubernetes

05.06.2021 16:05:21 | Автор: admin

Стандартная утилита для управления кластером Kubernetes (kubectl) достаточно мощный инструмент администрирования, отладки и мониторинга. Но если вы хотите получить сводную информацию о кластере Kubernetes или работать с несколькими ресурсами одновременно, она уже не так эффективна.

Сегодня существует большое количество графических панелей для управления Kubernetes, вероятно, вы уже используете одну из них. Например, это может быть стандартный Kubernetes Dashboard или панель управления и мониторинга, предоставляемая вашим облачным провайдером. Все эти инструменты очень похожи по методу группировки информации (простая иерархия для пространств имен / ресурсов) и не могут быть легко использованы для ответов на общие вопросы, касающиеся вашего кластера.

В качестве примера предположим, что у вас большой кластер Kubernetes и вы хотите своевременно получать ответы на следующие вопросы:

  1. Сможете ли вы быстро найти все Pods, для которых нет ограничений по ресурсам?

  2. Сможете ли вы быстро найти все Role Bindings, которые не используются?

  3. Сможете ли вы быстро определить, какое пространство имен утилизирует больше всего ресурсов в кластере?

  4. Можете ли вы быстро определить, какие образы используют тег "latest"?

Столкнувшись с этими вопросами, администраторам кластера приходится делать выбор. Либо терять время, используяkubectl, либо создавать специальные сценарии или инструменты, которые исследуют кластер и пытаются найти эти конкретные проблемы.

Было бы неплохо, если графический инструмент мог сразу же ответить на эти вопросы.

Этот отсутствующий графический инструмент Kubevious. Вы можете увидеть живую демонстрацию на https://demo.kubevious.io/, а посмотреть исходный код на https://github.com/kubevious/kubevious.

Переосмысление возможностей Kubernetes Dashboard

Kubevious это революционная панель управления Kubernetes, которая намного функциональнее существующих аналогов. Она имеет множество интересных функций, но в этой статье мы сосредоточимся на описании мощного механизма правил, который позволяет вам находить и группировать ресурсы Kubernetes для решения поставленной задачи.

Kubevious dashboardKubevious dashboard

Kubevious имеет иерархическую структуру, которая будет вам знакома, поскольку он группирует объекты по пространству имен и группирует их в древовидной структуре непосредственно под пространством имен. Однако настоящее волшебство случается, когда вы понимаете, что Kubevious выполняет дополнительный анализ для каждого типа ресурса и маркирует его в соответствии со встроенными или настраиваемыми правилами.

Например, вы без дополнительных настроек увидите значок шпион на объектах, которые имеют доступ к Kubernetes API за пределами их собственного пространства имен.

Spy ObjectsSpy Objects

Возможность получить подобную информацию, особенно в больших кластерах, очень важна, так как значительно сокращает время, затрачиваемое на использование kubectl.

В Kubevious также есть несколько других встроенных маркеров, которые могут вам пригодиться. В качестве примера вы можете легко найти неиспользуемые RoleBindings:

Unused Cluster role bindingsUnused Cluster role bindings

Вы можете создавать собственные правила для решения других задач.

Рассуждения о ресурсах Kubernetes

Kubevious имеет собственный механизм правил, который позволяет находить ресурсы Kubernetes с указанными вами характеристиками. Редактор правил также является частью GUI:

Rule editorRule editor

Каждое правило определяется на простом языке Kubik, синтаксис которого напоминает Javascript. Для каждого правила вы определяете тело правила (какой ресурс Kubernetes искать) вместе с маркером (что делать с затронутым ресурсом). Маркеры представляют собой комбинацию значка, имени и цвета, которую вы можете использовать для маркировки затронутых объектов.

Уже существуют библиотеки правил, которые вы можете повторно использовать в своем кластере. Например, вы можете быстро найти модули без ограничений по ресурсам:

Pods without limitsPods without limits
for(var container of item.config.spec.containers){  if (!container.resources.limit)  {    warning('No resource limit set');  }}

В качестве другого примера найдём пространство имен с ресурсами, которые потребляют более 40% ЦП или памяти.

select('Namespace')    .filter(({item}) => {        const cpu = item.getProperties('cluster-consumption').cpu;        const memory = item.getProperties('cluster-consumption').memory;        return (unit.percentage(cpu) >= 40) ||                          (unit.percentage(memory) >= 40);    })

Правила редактируются с помощью живого редактора из графического интерфейса пользователя и мгновенно сохраняются в самом кластере. Никаких дополнительных инструментов для управления правилами не требуется.

Вы можете найти дополнительную информацию о Rule Engine в официальной документации.

Перекрестные проверки и корреляции ресурсов

Другая впечатляющая возможность Kubevious это корреляция между различными ресурсами Kubernetes. После создания правила, которое соответствует определенному количеству ресурсов, вам не нужно вручную прокручивать область просмотра, пытаясь найти, какие из них подходят.

GUI предоставляет вам информацию о том, какие ресурсы затронуты:

Affected resourcesAffected resources

При нажатии на любой из затронутых ресурсов панель управления отобразит сведения об этом ресурсе.

Эта возможность корреляции работает всегда, независимо от выбора объектов Kubevious. Таким образом, если Kubevious может обнаруживать связь между определенными ресурсами, вы сможете её увидеть на общей панели.

Shared resourcesShared resources

Заключение

Механизм правил это лишь одна из функций, предлагаемых Kubevious. Kubevious имеет несколько других интересных функций, таких как полнотекстовый поиск всех ресурсов кластера, а также машина времени для обнаружения изменений конфигурации. Если вы управляете большими кластерами и не хотите больше играть в вопросы с kubectl, то Kubevious легко установить и контролировать в своем кластере.

Посетите сайт https://kubevious.io/ для получения дополнительной информации.

Подробнее..

Хочу больше годных профстатей, Хабр

21.06.2021 10:15:25 | Автор: admin

Листая страницы Хабра, поймал себя на мысли, что я воспринимаю Хабр как новостную ленту в социальной сети. То есть как нечто, что прямого отношения лично ко мне не имеет и касается меня очень косвенным путем. Нечто полуразвлекательное-полупознавательное.

Ну, судите сами. Вот примерный список тем, которые превалируют на Хабре.

  1. Что там новенького у Илона Петровича Маска.

  2. Как с помощью Arduino, говна и палок сделать годный фаллоимитатор радиоприемник.

  3. Как я ушел с прошлой работы, и как мне было там плохо.

  4. Как я нашел свою текущую работу, и какая она крутая.

  5. Как живется специалисту X в стране Y.

  6. Какой путь нужно проделать фельдшеру из Ангарска, чтобы стать тестировщиком мобильных приложений в Ирландии.

  7. Обсуждение новомодной платформы для веб-разработки, которая через 3 года станет старомодной.

  8. Промываем косточки крупным компаниям.

  9. Исторические экскурсы в IT/технологии/медицину.

  10. Реклама компаний.

  11. Мнения обо всем отвлеченном на свете.

  12. И т. д.

Все эти темы и все статьи неплохие, интересные. Но я хотел бы другого.

Я давно программирую на С++/qt. Думаю, что могу создать с нуля любой программный продукт (desktop) . Могу набирать команды, могу выстраивать отношения с заказчиками и т.д. Периодически приходится запускать (создавать) новые направления/программные продукты. Однако время, затрачиваемое мной и командой на каждый новый продукт, остается примерно постоянным. Вернее, не совсем так. Время суммарной работы оказывается в прямой пропорциональной зависимости от сложности продукта (объема кодовой базы). То есть постоянной величиной оказывается производительность работ, или эффективность труда. На всякий случай оговорюсь, что речь не идет о новичках, в команде только опытные толковые сотрудники.

Так вот, эту самую производительность труда очень хотелось бы увеличить. Как и в чем мне может помочь Хабр? Или я зря надеюсь?

Мне хотелось бы поднимать свой профессиональный уровень серьезными профессиональными статьями по проектированию/созданию/ведению больших продуктов. Статьями, которые по легкости восприятия были бы такого же классного уровня, как и сейчас большинство статей на Хабре. Но статьями, которые по глубине и полезности были бы как классические книги Скотта Майерса, банды четырех, Алана Купера, Роберта Мартина и др. Знаете, читая эти книги, я прибавлял каждый раз в квалификации. К сожалению, читая статьи на Хабре, я этого не чувствую. Даже более того: не могу припомнить случая, когда я хотел изучить какой-то новый для меня (и обычно нетривиальный) нюанс и находил бы его на Хабре. Я находил его где угодно, но только не на Хабре. Или вообще не находил.

Посему я очень жду и буду приветствовать появление на Хабре статей по следующим направлениям.

Новые шаблоны проектирования (С++)

Да, я знаю, что шаблоны не догма и не панацея, и всё всегда можно придумать и самому. Но я также знаю, что это проверенная годами экономия времени архитекторов и программистов. Это кругозор, который (при его наличии) позволяет делать сложную работу быстрее, а то и вообще моментально. У меня сложилось ощущение, что в мире С++ развитие шаблонов практически остановилось на известной книге банды четырех. Там описано 23 шаблона, и еще примерно столько же можно накопать в интернете. Я хочу больше! В моей практике были случаи, когда приходилось создавать шаблоны, разительно непохожие на известные. И приходилось тратить довольно много времени на приведение их к товарному использованию, хотя бы даже на продумывание такой терминологии, которая бы естественно бы воспринималась коллегами. Уверен, что если бы мы имели возможность в нужный момент найти и прочитать описание свежего шаблончика, наша работа местами была бы намного быстрее.

Думаю, на Хабре замечательно бы смотрелись статьи в классическом стиле банды четырех: описание и идеология шаблона, пример реальной задачи, код С++, границы применения.

Кстати, по шаблонам есть фундаментальный труд POSA: 5-томник на 2000+ страниц, перевода на русский язык до сих пор нет. Чем не непаханное поле?

Шаблоны ведения проектов в Git

Под этим можно подразумевать стратегии ветвления, схемы тестирования и создания релизов на ветках Git. Думаю, для многих было бы полезно появление серии статей в таком духе:

  1. Я веду маленький проект на 20 тысяч строк кода, в нем задействовано 5 человек, с системой контроля версий мы работаем вот так (и описать конкретно, вплоть до команд командной строки).

  2. Я веду неплохой проект на 100 тысяч строк кода, в нем задействовано 10 человек, и мы работаем вот так: схемы, команды git.

  3. Мы с тремя командами по 10 человек развиваем проект на 1 миллион строк кода, продаем продукт разным клиентам в разных конфигурациях, и всё свое хозяйство мы покрыли регрессионным тестированием. И для этого мы делаем вот это: схемы, команды git.

  4. У нас работает 200 человек, и у нас 10 миллионов строк кода в монорепе на 5 продуктов, и каждый продукт ещё поставляется в трех разных версиях. Мы опытным путем пришли, что только так: схемы, команды git.

  5. А у нас очень много кода и много микросервисов, каждый в своем репозитории, впрочем, есть и подрепозитории (подмодули). Часть кода покрыта тестами, часть нет. Раньше мы работали вот так, а теперь перешли на вот это (схемы, команды), но по эффективности труда одинаково приемлемо.

GUI

Дизайн GUI, новые подходы, примеры удачных и неудачных виджетов, примеры плохих и хороших интерфейсов/решений. Эта область очень динамично развивается в плане того, что мы видим на экранах своих смартфонов и мониторов (причем на смартфонах более динамично, чем на мониторах). Однако в плане научного описания, осмысления и систематизации всё как-то вяло. То есть решений полно, но ими никто не делится. А если и делится, то как-то не очень доходит до конкретных исполнителей, и те пишут с нуля. Думаю, ситуация бы улучшилась, если бы появилась серия статей в таком духе:

  1. Камрады, я внедрил к себе гамбургер-меню на 500 строк (qt) вот отсюда (ссылка). Вот, смотрите: скриншот, gif-анимация. Работает чётко! Лицензия LGPL. Короче, пользуйтесь все.

  2. Я создал свой виджет ввода паролей. Он круче, чем другие по таким-то причинам. Делюсь! Ссылка на репозиторий, скриншоты.

  3. Я смог объединить панели меню и тулбаров в одной панели. Получился принципиально новый виджет, похожий одновременно и на меню, и на тулбары. Область применения вот такая, описываю. Скриншоты, описание даю, а вот код нет!

  4. Я потратил 3 года на то, чтобы изучить все подходы к созданию справочных систем для больших приложений, и хочу об этом детально рассказать, с кучей примеров.

  5. Раньше у нас был такой интерфейс, и такие задачи. А потом добавилась еще одна, и мы в корне переделали интерфейс. Рассказываю, почему прошлый интерфейс был оптимален, а текущий супероптимален.

  6. Я создал свою библиотеку виджетов, да еще с библиотекой стилей. Ссылка. Область применения такая-то. Могу продать хорошим людям (или подарить).

  7. Я супермегадизайнер, и на примере 30 известных приложений за последний год объясню вам, что попало в тренд, что не попало, а что создает будущий тренд.

Успешный опыт решения важных и нетривиальных задач из самой близкой действительности

Знаете, меня не сильно цепляют новости, о том, какие планы у Джеффа Безоса на космос или как Boston Dynamics обучает своего пса. Это не увеличивает мою зарплату. Я хочу чего-то более близкого и понятного мне, но самое главное применимого в моей работе.

К примеру, я делаю что-то похожее на графический редактор, и мне хотелось бы иметь инструментарий быстрого выделения человека по контуру одним щелчком. Или автоматического определения границ стран на простой разноцветной растровой карте. Или быстрого (до микросекунды) и легковесного (до пары мегабайт конфигов) определения страны по геокоординатам точки.

Сложные ли это задачи? Непростые, но решаемые. И некоторые из них решены многократно самыми разными производителями софта.

Давайте еще пример. Я запускаю приложение X и параллельно еще парочку для полного занятия вычислительных ресурсов (например, конвертор видео). И вот в приложении X вижу слайд-анимацию, который замечательно себя ведет (нисколько не тормозит) при том, что соседние приложения на 100% заняли мой процессор. Это неплохой результат для X, и, черт возьми, я хочу знать, как они этого добились.

Да, я знаю, что есть такие понятия, как коммерческие секреты, патенты и всякое такое. Но ведь несложно же из груды решенных задач вытащить что-то такое, что давно уже не представляет коммерческой ценности и поделиться этим с программистским сообществом. Подозреваю, что в команде разработки, к примеру, Adobe Photoshop найдется немалая кипа примеров изящно решенных нетривиальных задач, которые незаслуженно скрыты от мира. И если бы их открыть в виде конкретных формул, диаграмм (а в идеале исходного кода) и разместить в удобочитаемом виде на Хабре, это было бы очень многим полезно.

Вместо послесловия

Изложенные мной хотелки уже присутствуют в некотором небольшом количестве на Хабре, а помимо этого, подобную информацию по крупинкам можно собирать на конференциях, искать в книгах и некоторых научных журналах. Но трудозатраты слишком велики! Бывает, читаешь книгу в 500 страниц, чтобы найти хоть намек на решение, а его там не оказывается. Или, того хуже, оказывается, но такой, что лучше бы его не было Статьи в научных журналах часто оказываются написанными не с целью поделиться со всем миром своими достижениями, а просто формально отчитаться за очередной грант. Да и вообще, кажется, что печатные научные журналы по программированию эволюционно проигрывают интернет-платформам.

В общем, хочется надеяться, что Хабр сможет подобрать это непростое знамя нести серьезную, неразвлекательную науку в профессиональные сообщества, но делать это легко и изящно. И создавать условия для современных Скоттов Майерсов, готовых писать статьи для русскоязычной аудитории и потом компилировать их них свои бестселлеры. Надеюсь, жду, верю И всем не болеть!

Подробнее..

Киоск Raspberry Pi для графического интерфейса на Kivy

25.08.2020 20:14:31 | Автор: admin
Привет, Хабр!

Хочется поделиться опытом настройки Raspberry Pi 3B+ в качестве киоска с GUI на базе библиотеки Kivy для Python 3. Почему именно Kivy? Просто мы уже имеем продукт, разработанный на Python, нам бы хотелось добавить к нему графический интерфейс. Стоит отметить, что до Kivy мы перепробовали несколько вариантов, включая wxWidgets и даже браузер на Chromium с веб-приложением. Все эти альтернативы оказались бессильны против Kivy, лёгкой и быстрой.

Окружение


Мы будем использовать Raspbian Lite с Python 3.7 и системой управления процессами и сервисами Supervisor. Кстати, очень удобной является утилитка Raspberry Pi Imager, с помощью которой можно подготовить SD-карточку. После первой загрузки нашего малыша RPi логинимся с помощью стандартного логина pi и пароля raspberry. Далее выполняем:
$ sudo raspi-config

Выбираем пятый пункт Interfacing Options, в появившемся меню нас интересует второй пункт SSH, с помощью которого мы включим удалённый доступ для удобства.
Итак, удобно откинувшись в любимом кресле, продолжим настройку RPi через любой удобный ssh-клиент.

Пользователь


Давайте создадим пользователя с удобным для нас именем, разрешим ему пользоваться sudo и перезагрузимся:
$ sudo useradd -m kivygui -s /bin/bash$ sudo passwd kivygui$ sudo usermod -a -G sudo kivygui$ sudo reboot

После перезагрузки залогинимся через ssh уже с новыми данными для kivygui и удалим стандартную учётку pi:
$ sudo userdel pi$ sudo rm -r /home/pi/

Не лишним будет указать, что мы пользуемся американской раскладкой клавиатуры:
$ sudo sed -i 's/XKBLAYOUT="gb"/XKBLAYOUT="us"/' /etc/default/keyboard

Тестовое приложение


Наше тестовое приложение будет находиться в отдельной папке, создадим её:
$ mkdir /home/kivygui/helloworld

Теперь давайте в текстовом редакторе nano создадим скрипт для запуска Python-приложения /home/kivygui/helloworld/run.sh со следующим содержимым:
export DISPLAY=:0.0xset s off -dpmsexec matchbox-window-manager &while true; do  exec python3 start.pydone

Сделаем в nano и пример простенького интерфейса в файле /home/kivygui/helloworld/start.py:
import kivykivy.require('1.11.0')from kivy.app import Appfrom kivy.uix.label import Labelclass MyApp(App):    def build(self):        return Label(text='Hello, world!')if __name__ == '__main__':    MyApp().run()

Графический пользовательский интерфейс


Нам понадобится pip3:
$ sudo apt-get update -y$ sudo apt-get install -y python3-pip

Также я сталкивался с тем, что при установке nodm не всегда загружались все необходимые пакеты, поэтому на всякий случай установим их заранее:
$ sudo apt-get install -y desktop-base gtk2-engines-pixbuf libxklavier16 xserver-xorg xserver-xorg-input-all xserver-xorg-input-libinput xserver-xorg-input-wacom xserver-xorg-legacy xserver-xorg-video-all xserver-xorg-video-amdgpu xserver-xorg-video-ati xserver-xorg-video-fbdev xserver-xorg-video-nouveau xserver-xorg-video-radeon xserver-xorg-video-vesa

Теперь давайте установим nodm и оконный менеджер matchbox:
$ sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -y x11-xserver-utils nodm matchbox-window-manager$ echo "/usr/sbin/nodm" | sudo tee /etc/X11/default-display-manager$ sudo sed -i -e "s/NODM_ENABLED=false/NODM_ENABLED=true/" -e "s/NODM_USER=root/NODM_USER=kivygui/" -e "s/NODM_X_OPTIONS='-nolisten tcp'/NODM_X_OPTIONS='-nolisten tcp -nocursor'/" /etc/default/nodm

Настал черёд Kivy:
$ sudo apt-get install -y libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev pkg-config libgl1-mesa-dev libgles2-mesa-dev python-setuptools libgstreamer1.0-dev git-core gstreamer1.0-plugins-{bad,base,good,ugly} gstreamer1.0-{omx,alsa} python-dev libmtdev-dev xclip xsel libjpeg-dev$ sudo python3 -m pip install --user kivy

Теперь научим нашу систему показывать нам графический интерфейс вместо консольного приглашения, скроем все диагностические сообщения и покажем графический экран загрузки системы:
$ sudo rm /etc/systemd/system/default.target$ sudo rm /etc/systemd/system/getty@tty1.service.d/autologin.conf$ sudo ln -s /lib/systemd/system/graphical.target /etc/systemd/system/default.target$ sudo sed -i '$ s/$/ quiet splash consoleblank=0 loglevel=0 logo.nologo plymouth.ignore-serial-consoles/' /boot/cmdline.txt$ sudo sed -i 's/console=tty1/console=tty3/' /boot/cmdline.txt

Если есть желание, консоль tty1 вообще можно отключить:
$ sudo systemctl disable getty@tty1

Supervisord


Теперь давайте установим Supervisor:
$ sudo apt-get install -y supervisor

Создадим папку для логов:
$ mkdir /home/kivygui/logs

Теперь давайте ненадолго остановим службу Supervisor для переконфигурации:
$ sudo systemctl stop supervisor

Добавим с помощью редактора nano в кофигурационный файл /etc/supervisor/supervisord.conf следующее:

[program:rungui]
command=sh run.sh
directory=/home/kivygui/helloworld
user=root
autostart=true
autorestart=true
startsecs = 5
startretries=3
stderr_logfile=/home/kivygui/logs/rungui.err.log
stdout_logfile=/home/kivygui/logs/rungui.out.log
stderr_logfile_maxbytes=5MB
stdout_logfile_maxbytes=5MB
stopsignal=INT
stopwaitsecs=5

Также давайте дадим пользователям kivygui и root дополнительные возможности. Для этого воспользуемся командой:
$ sudo visudo

Приведём файл к следующему виду:

#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults env_reset
Defaults mail_badpass
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# Host alias specification

# User alias specification

# Cmnd alias specification

# User privilege specification
root ALL=(ALL:ALL) ALL
kivygui ALL=(ALL:ALL) ALL

# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d

kivygui ALL = NOPASSWD: /usr/bin/supervisorctl
kivygui ALL = NOPASSWD: /usr/bin/python3.7
kivygui ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload
kivygui ALL=(ALL) NOPASSWD: /usr/bin/supervisord

root ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload
root ALL = NOPASSWD: /usr/bin/supervisorctl
root ALL = NOPASSWD: /usr/bin/python3.7
root ALL=(ALL) NOPASSWD: /usr/bin/supervisord

Теперь мы можем запускать службу:
$ sudo systemctl start supervisor

На подключенном к RPi мониторе мы увидим заветное приветствие. Осталось только перезагрузиться, чтобы проверить работу графического экрана загрузки.

PS:


На самом деле, nodm можно заменить lightdm с автологином. Это будет абсолютно аналогичное nodm решение. Тем более, сам разработчик nodm рекомендует этот подход.
Подробнее..

Фреймворки и библиотеки для кроссплатформенной разработки десктопных программ

18.11.2020 00:09:51 | Автор: admin

В этой статье я расскажу вам о некоторых самых популярных фреймворках, которые помогут вам при разработке программ для компьютеров под управлением Windows/MacOS/Linux. Если вы опытный программист, то вряд ли найдете в этой статье что-то новое для себя. Она скорее для новичков, которые ищут простое решение своей проблемы быстрое написание хорошей (на сколько это возможно) кроссплатформенной программы.

Что такое кроссплатформенная разработка?

Что вообще из себя представляет кроссплатформенная разработка? Если вам нужно, что бы ваше приложение работало сразу на нескольких операционных системах с минимальными затратами человеко ресурса, то скорее всего вы прибегнете именно к этому. Вы разрабатываете одну программу, пишете один код, а он запускается на всех поддерживаемых платформах. Удобно! Но стоит различать кроссплатформенность и нативность.

Если вкратце, то кроссплатформенность это способность программного обеспечения работать с несколькими аппаратными платформами или операционными системами. Нативные программы в свою очередь пишутся для работы на определенной аппаратной или программной платформе. Из этого можно сделать вывод: если ваша программа будет работать с железом, то скорее всего даже в кроссплатформенной программе вам придется прописывать нативный функционал для конкретной платформы.

В чем же преимущество кроссплатформенной разработки? Во-первых, вам нужно задействовать меньше людей (не нужно содержать DotNet разработчика, Swift/objective C разработчика и всех к ним прилагающихся), во вторых вы охватываете большее количество устройств -> больше людей смогут пользоваться вашей программой.

Также подобный принцип не лишен недо]статков плохая нативность, оптимизация тоже будет хуже, чем у такой же программы написанной под конкретную платформу. Но иногда бывает такое, что вам очень нужно, что бы ваше приложение поддерживало несколько платформ, но разрабатывать под каждую возможности нет. Тогда на помощь приходят уже созданные Фреймворки и технологии для разработки кроссплатформенных GUI. Вот некоторые из них:

Electron JS

Недавно я написал статью о том, как на Electron JS написать "Hello World". В комментариях поднялась дискуссия о "за и против" этой технологии. До сих пор очень многие разработчики ведут споры относительно того, стоит ли использовать Electron в своих проектах или нет. Electron JS это JavaScript фреймворк, позволяющий вам написать полноценную программу используя Web Технологии HTML & CSS, JS. Electron JS представляет собой движок Chromium в котором и исполняется весь ваш код. У Electron'a есть один, но достаточно серьезный недостаток большое потребление памяти, как физической, так и оперативной. Всем давно известно, какой прожорливый Chrome, а мы на него еще своего кода хотим навесить. Но если посмотреть с другой стороны: сегодня многие популярные приложения написаны на Electron'e Slack, Skype, Discord, VSCode, Atom, Postman, Insomnia и т.д. А с учетом непрекращающегося роста мощности компьютеров все реже приходится слышать от пользователей, что ваш хром съел всю мою память. Высокое потребление памяти не будет играть большой роли если продукт будет хорош в своей сфере, будет грамотно написан код и распределены процессы

Официальный сайт

Плюсы

  • Использование наработок из Web

  • Просто найти (или "воспитать") специалиста

  • Качественная (и русифицированная) документация

  • Поддержка сообщества и GitHub

Минусы

  • Высокое потребление памяти (физическая и ОЗУ)

  • Легко написать плохой код

  • Плохая нативность


NW.JS

Так же как и Electron, NW.JS позволяет вам создавать кроссплатформенные приложения с использованием Web технологий. Сегодня NW.JS Спонсируется компанией Intel, и разрабатывается сообществом. NW.JS не может похвастаться таким же богатым списком проектов как Electron, но это все равно очень хороший фреймворк для создания кроссплатформенных приложений. Так же как и Electron, NW.JS тащит за собой движок Chromium и все вытекающие из него проблемы. NW.JS очень похож по своему принципу на Electron, но все-же различия есть. Могу порекомендовать вам эту статью для ознакомления.

Плюсы

  • Использование наработок из Web

  • Просто найти (или "воспитать") специалиста

  • Качественная (и русифицированная) документация

  • Поддержка сообщества и GitHub

Минусы

  • Высокое потребление памяти (физическая и ОЗУ)

  • Легко написать плохой код

  • Плохая нативность


Qt/QML

Qt очень мощный набор инструментов для создания кроссплатформенных приложений на языке С++ и Python (а также Ruby, PHP, C# и др. но они в отличии поддерживаются сообществом). Qt уже достаточно старый фреймворк, но он продолжает активно развиваться и на нем написаны такие программы как: 2ГИС для Android, Kaspersky Internet Security, Virtual Box, Skype, VLC Media Player, Opera и другие. Известное многим линукс-юзерам окружение рабочего стола KDE тоже написанной с использованием Qt. Qt имеет среду разработки Qt Creator, которая включает в себя Qt Designer с помощью которого можно создавать графический интерфейс. Визуальное создание интерфейса позволяет легко и просто создавать интерфейс, перетаскивая различные виджеты (выпадающие списки, кнопки, переключатели) на форму. Qt имеет обширную документацию, активную поддержку сообщества и множество других преимуществ.

Официальный сайт

Плюсы

  • Визуальный редактор интерфейса

  • Огромное количество модулей в составе фреймворка

  • Поддержка большого количества языков программирования (официально только Python и C++, остальные поддерживаются сообществом) -> хорошая нативность при грамотном коде

  • Низкий порог вхождения

Минусы

  • Большой вес приложения

  • Иногда возникают проблемы с компиляцией под windows


Avalonia

Достаточно молодой и активно развивающийся фреймворк для кроссплатформенной разработки, разработанный и поддерживаемый компанией Microsoft и сообществом. Avalonia позволяет создавать интерфейсы на основе XAML. Сама Avalonia основана на WPF/UWP. Код пишется на языке C#. Благодаря XAML Avalonia позволяет создавать гибкие и стилизованные интерфейсы. Имеет хорошую документацию, хоть и не большое, но очень приветливое рускоязычное сообщество.

Официальный сайт

Плюсы

  • Хорошая нативность (+поддержка системных декораций)

  • Активное и приветливое сообщество, как русскоязычное, так и англоязычное (чаты, форумы)

  • Заимствование и сходство с WPF (Если вы работали с WPF вам будет легко разобраться с Авалонией, и при возникновении трудностей, код написанный для WPF скорее всего заработает у вас на авалонии)

  • Стили (стили в Авалонии имеют сходство с CSS, что упрощает написание этих самых стилей)

Минусы

  • Маленькое сообщество (хоть и приветливое, но в силу молодости проекта, еще очень немногочисленное сообщество разработчиков)

  • Маленькое количество проектов (вы можете застрять с тем, что никто не решал такую задачу, которая встала перед вами)


WxWidgets/WxPython

wxWidgets (ранее известная как wxWindows) это кросс-платформенная библиотека инструментов с открытым исходным кодом для разработки кроссплатформенных на уровне исходного кода приложений, в частности для построения графического интерфейса пользователя (GUI).Она разработана не только для того, чтобы создавать GUI. Она также имеет набор классов для работы с графическими изображениями, HTML, XML документами, архивами, файловыми системами, процессами, подсистемами печати, мультимедиа, сетями, классы для организации многопоточности, отладки, отправки дампов и множество других инструментов. Имеет обертку для языка Python -WxPython. С использованием этой библиотеки написанны такие программы как: FileZilla, AudaCity, BitTorrent и другие.

Официальный сайт

Минусы

  • Мало современной информации в интернете

  • Не очень активное сообщество

Плюсы

  • Простое написание интерфесов

  • поддержка разных языков программирования


Tkinter

Tknter - кросс-платформенная событийно-ориентированная графическая библиотека разработанная Гвидо ван Россумом (создатель языка Python), позволяет написать вам простой, но функциональный интерфейс для вашего проекта на Python. По сути Tkinter представляет из себя пакет для Python, предназначенный для работы с библиотекой Tk. Библиотека Tk содержит компоненты графического интерфейса пользователя (graphical user interface GUI), написанные на языке программирования Tcl.

Официальный сайт

Минусы

  • Примитивные интерфейсы

  • Для одного языка

Плюсы

  • Простое написание интерфесов

  • Позволяет вам быстро написать простой GUI для вашей Python программы


Итог

В этой статье я рассказал далеко не о всех фреймворках, но о самых популярных, активно поддерживаемых и подходящих для абсолютно разных проектов и задач. Призываю вас в комментариях рассказать о своем опыте работы с различными фреймворками (но только с теми, что есть в статье), о том какие плюсы и минусы вы видите в кроссплатформенной разработке?

Подробнее..

Свободный мини AUTOSAR редактор для микроконтроллеров

11.01.2021 20:23:30 | Автор: admin

В этом посте я хочу поделиться с вами программой написанной мной для разработки ПО под микроконтроллеры по стандарту близкому к AUTOSAR.

В 2018 году меня позвали работать в EPAM Беларусь на аутсорсный проект по разработке ПО для рулевых реек. Я с большим удовольствием согласился и уехал жить и трудиться в Беларусь.

На этом проекте я познакомился со стандартом разработки ПО в автомобильной промышленности - AUTOSAR.

Пару слов об AUTOSAR

Согласно стандарту, архитектура приложения разделяется на 3 уровня:

  • Application;

  • Run Time Environment (RTE);

  • Basic software.

Главная идея стандарта в том, чтобы разделить систему на компоненты, а также определить уровни ответственности каждого из уровней. При этом application в основном стараются делать независимым от железа, чтобы можно было безболезненно переносить ПО с одного МК на другой. Система разделяется на независимые компоненты, и они взаимодействуют друг с другом с помощью RTE. Это позволяет иметь возможность протестировать каждый компонент в отдельности и добиться практически 100% покрытия кода тестами.

Подробнее о стандарте можно почитать здесь.

Согласно стандарту, компоненты могут иметь следующие составляющие:

  • Порты для взаимодействия друг с другом;

  • Функции, вызываемые по событиям из RTE (runnables);

  • Калибровочные параметры Calibration Data (CData);

  • Память принадлежащая отдельному экземпляру компонента Per Instance Memory (PIM);

  • Переменные взаимодействия между runnables одного компонента Inter Runnable Variable (IRV).

Взаимодействие runnables компонента с его портами CData, PIM, IRV происходит с помощью функций RTE.

Взаимодействие компонентов проекта между собой происходит с помощью портов. Существует несколько видов портов, и они образуют между собой пары, которые могут быть соединены друг с другом. Выделим главные из них: sender-receiver, client-server.

  • Sender порт порт для записи в порт;

  • Receiver порт порт для чтения из порта;

  • Server порт предоставляет клиентам определенную функциональность;

  • Client порт позволяет вызывать функционал другого компонента.

На картинке ниже представлен пример взаимодействия компонентов SWC1 и SWC2, а также внутренняя структура SWC1.

Картинка взята отсюда.

AUTOSAR GUI Editor

Меня очень вдохновила концепция разбития системы на отдельные независимые компоненты, и я решил написать свой собственный небольшой автозар редактор, который бы удовлетворял нужды моего домашнего проекта. С этим редактором я и хочу с вами поделиться. Конечно, весь функционал стандарта в редакторе мне не удалось реализовать, также пришлось отойти от сохранения файлов проектов в формате arxml. На данный момент реализованы следующие возможности:

  • Создание компонентов и композиций;

  • Создание client-server и sender-receiver интерфейсов;

  • Создание простых типов данных с указанием разрешенного диапазона. Этот диапазон может применяться на этапе тестирования компонента;

  • Создание комплексных типов данных(struct), массивов и перечислений;

  • Создание портов в компонентах и композициях и задание им их интерфейсов;

  • Соединение портов, имеющих одинаковый интерфейс, при этом один write порт может быть подключен к нескольким read портам, также как и server порт к нескольким клиентским портам;

  • Возможно размножение компонентов одного типа (Multiple instantiation);

  • Возможно создание PIM и CData для компонентов, при этом для каждого образца компонента можно задать своё индивидуальное начальное значение для PIM и CData;

  • Поддерживается создание периодически запускаемых runnable компонента, каждый раннабл может иметь свою частоту вызова;

  • Создание задач планировщика с приоритетами и их собственными периодами и распределение runnables компонентов по этим задачам;

  • Автоматическая генерация RTE файлов, которые объединяют компоненты друг с другом согласно проекту;

  • Автоматическая генерация шаблонов компонентов для первоначального использования этих файлов в проекте;

  • Генерация test environment для отдельно взятого определения компонента;

  • Генерация планировщика вызова runnables;

  • Проверка проекта на ошибки.

Есть и определенные ограничения на данный момент:

  • Во вложенных композиции не может быть вложено других композиций, все композиции находятся в Main композиции.

  • Отсутствуют асинхронные клиент-серверные операции;

  • Первоначально я сделал генерацию тасок ОС как тасок FreeRTOS, но ввиду того, что у меня периодически отказывала одна из тасок и не хотелось отлавливать баг в программе, я решил переписал планировщик таким образом, чтобы ОС таски вызывались одна за другой в соответствии с их приоритетом и отойти от использования FreeRTOS в моём домашнем проекте. Генерация фриртосных тасок осталось в анналах git истории редактора.

Сам исходный код редактора я выложил на гитхаб в открытый доступ.

По умолчанию, при открытии программы создается пустая композиция Main. Далее можно начать создавать структуру проекта, компоненты, интерфейсы.

Создание элементов проекта

Создание компонентов, композиций, интерфейсов и типов данных делается с помощью выбора необходимого элемента в одном из пунктов меню insert.

Далее, как только необходимый элемент был создан, откроется окно для его настроек.

Чтобы поместить компонент в композицию, необходимо создать его definition, затем выбрать в дереве объектов композицию, в которую необходимо добавить образец компонента, и после перетащить definition в композицию(drag and drop). Если всё выполнено верно, то в композиции появится новый компонент. Для того, чтобы сделать возможность создавать несколько образцов компонента, необходимо поставить галочку Multiple instantiation. В меню компонента также можно задать ему ранаблы, порты, PIM и CData.

Пример простого проекта из 4х компонентов.Пример простого проекта из 4х компонентов.

Для того, чтобы создать порт для компонента, необходимо создать сперва интерфейс для данного типа порта, создать порт в defenition компонента, а уже потом выбрать интерфейс порта.

Для того чтобы задать init values для PIM и CData необходимо выбрать компонент в композиции и во вкладке Component properties вписать нужные данные в поля.

Чтобы создавать соединения между портами компонентов, необходимо чтобы порты соединяемых компонентов имели один и тот же интерфейс. Нажав Insert->Connection зажимаем ЛКМ над одним портом компонента, а затем отжимаем её над другим. Если всё хорошо, то появится прямая линия, соединяющая порты компонентов. Можно добавить несколько точек, в соединительную линию, чтобы она обходила элементы.

Перемещение по композиции

Зажав колесико, можно перемещать композицию

Покрутив колесико - изменять масштаб

Двойной клик по колесику - растягивает текущую композицию в отображаемое окно

Перемещение компонентов по композиции осуществляется путем зажатия ЛКМ на компоненте, перемещения, а затем отпускания ЛКМ.

Размеры компонентов можно менять с помощью якорей по углам.

Планировщик

Настройка планировщика осуществляется с помощью окна Runnables management, которое может быть запущено с помощью System-> Runnables order. Для того, чтобы планировщик был корректно создан, необходимо сперва создать таски ОС и затем распределить ранаблы компонентов по этим таскам.

Создание и редактирование задач планировщика.Создание и редактирование задач планировщика.Распределение runnables по задача планировщика.Распределение runnables по задача планировщика.

На данный момент поле Stack size in bytes не используется. Но при необходимости, можно создать(откопать в git истории) генератор планировщика, который создает таски RTOS с указанным значением размера стэка.

Генерация RTE

Перед генерацией RTE очень благоприятно убедиться в отсутствии ошибок в проекте, например не указанных интерфейсах в портах или пропущенных типов данных в структурах. Делается это с помощью команды System->Check errors.

Далее необходимо указать место, куда будут сохраняться сгенерированные RTE файлы и шаблоны компонентов в окне Project Settings. (Project->Settings). Здесь же необходимо будет указать частоту системного таймера, на котором будет основываться расчет вызовов задач планировщика.

Если были указаны относительные пути, как на картинке выше, то папки с файлами будут сгенерированы относительно места расположения проекта.

После того, как все ошибки устранены и указаны пути сохранения, жмем Project->Generate RTE и получаем папку RTE и папки с шаблонами компонентов. Важно знать, что файлы в папках RTE и RteSkeleton всегда будут перезаписываться при перегенерировании RTE.

Настройка проекта в STM32CubeIDE

Пример выполнен на STM32CubeIDE, чтобы показать, как можно использовать порты, runnables, таски и СData с помощью моего редактора.

Рассказывать как сделать проект в STM32CubeIDE я не буду, т.к. по этому поводу уже написано множество статей. Скажу лишь основные действия, которые необходимо выполнить, чтобы проект собирался и запускался:

  1. Необходимо создать папку Components в корне проекта и скопировать в неё сгенерированные шаблоны компонентов

  2. Написать код для компонентов или просто оставить как есть, чтобы убедиться для начала, что проект собирается.

  3. Добавить в Includes проектакаждую include папку компонента, а также папку RTE.

4. Добавить в Source Location папки Components и RTE

5.В main.c добавить:


#include "Rte_Task_Scheduler.h"
а также вызывать работу планировщика в бесконечном цикле.

6.В stm32f4xx_it.c между /* USER CODE BEGIN Includes */ и /* USER CODE END Includes */ добавить:
#include "Rte_Task_Scheduler.h"

и также обновить обработчик прерывания SysTick_Handler.

7. После этого настройка проекта в кубе завершена и можно собирать проект и запускать его на железке.

Пример также выложил на github.

Конечно, AGE может содержать ошибки и ему есть ещё куда расти, но своей основной задачей генерацией RTE, он справляется на ура.

Если программа кого-нибудь заинтересует, в следующей статье расскажу, как делать тесты на компоненты.

Заключение

Данный редактор позволил мне сделать архитектуру моего приложения под STM32 более наглядной и гибкой. Если необходимо написать какой-либо математически нагруженный компонент я его могу теперь безболезненно протестировать в Eclipse и уже потом легко внедрить в проект в STM32. Также стал наглядно виден поток данных от одних компонентов к другим, что позволяет легко и быстро находить источники проблем в проекте, если они возникают.

Ссылка на AUTOSAR Gui Editor.

Ссылка на проект.

Подробнее..

Даже не пытайтесь повторить это в GUI

19.05.2021 10:20:52 | Автор: admin


Есть такое понятие, как дружественный пользователю Linux. Возникло оно оно очень давно, возможно через несколько минут после того, как Линус Торвальдс анонсировал свою разработку в листе comp.os.minix. Трудно сказать принесла-ли пользу данная концепция и различные её воплощения на рабочей станции. Понятно одно, что прогресс на этом пути довольно-таки ощутимо не совпадает с ожиданиями этого самого пользователя.

Возможно дело в том, что производители коммерческих дистрибутивов Linux и производители ПК стараются показать, что вполне реалистично использовать на рабочей станции Linux и при этом не использовать консоль в качестве основного инструмента управления ОС. Возможно, в будущем так и будет, но сегодня консоль на домашнем компьютере так же незаменима, как и на сервере и для этого есть веские основания.

Предлагаем рассмотреть несколько сценариев из реальной жизни, где хочешь не хочешь, все необходимо сделать в CLI. Таких примеров очень и очень много, это всего лишь пара кейсов в огромном множестве подобных.

Кейс 1 настройка пользовательского окружения


Большинство дистрибутивов Linux дают возможность графической установки ОС, которая для продвинутого пользователя Windows, или macOS не представляет из себя ничего сложного. Есть свидетельства в пользу того, что Ubuntu работает из коробки для пользователя, который впервые ставит Linux.

Однако дальше сразу возникает необходимость ручками внести правки в /etc/sudoers для того, чтобы пользователь имел права на исполнение sudo команд. Входить под учетной записью root в DE окружение не удастся, по умолчанию большинство Desktop Manager-ов отключают эту опцию. Придется вручную снимать эти ограничения в консольном режиме, вот и уловка 22. Остается visudo /etc/sudoers, или в крайнем случае vim /etc/sudoers из под супер пользователя.

Так выгладит моя правка файла.

|18:42:09|admin@srv:[~]> sudo diff /etc/sudoers /etc/sudoers.orig

85c85

< %wheel ALL=(ALL) NOPASSWD: ALL

---

> # %wheel ALL=(ALL) NOPASSWD: ALL


Нужно всего лишь отключить комментирование в соответствующей строке, после чего достаточно включить пользователя в группу wheel. Странно было бы это делать в графике, если можно запустить всего лишь gpasswd -a admin wheel из под пользователя root.

Но раз уж у нас есть права sudo надо уметь ими пользоваться. Самое первое для чего эти права понадобятся для установки и обновления программ. Можно конечно воспользоваться графическим фронтендом программ из репозитория, в конце концов даже Gentoo имеет GUI для своего portage. Однако ограниченность и второсортность этих средств настолько выпирают, что буквально подталкивают пользователя в сторону CLI.

Вы же не собираетесь вместо простого sudo aptitude update / sudo dnf update запускать графический фронтенд и беспомощно кататься вверх-вниз по списку пакетов. Если вы собираетесь оставаться на Linux всерьез и надолго, то необходимо освоить необходимый минимум консольных команд для вашего пакетного менеджера.

Чуть менее привычным делом является настройка шрифтов. Одной установкой шрифтов семейства Liberation, Noto, Dejavu и Droid дело не ограничивается. Нужно еще избавиться от использования древних шрифтов Microsoft из пакета corefonts. Проще всего их не ставить совсем, однако часто они проникают в систему, как зависимость для Wine, или других пакетов. В таком случае придется создать, или редактировать файл ~/.fonts.conf. Вот директива по избавлению от Arial.

<match target=pattern><test name=family qual=any><string>Arial</string></test><edit name=family binding=same mode=assign><string>Noto Sans</string></edit></match>

Хочу подчеркнуть, что проблема не в том, что используются шрифты Microsoft, а в том, что это очень старые версии их. Соответственно, на экране они выглядят не очень, мягко говоря. Только их и разрешено использовать в дистрибутивах Linux. Можно делать это в графическом редакторе, но файлы с точкой в начале имени придется хитрым способом открывать во встроенном файловом менеджере каждый раз, а это неудобно.

Кроме того, возможно придется шаманить с симлинками в /usr/share/fonts, или в /etc/fonts для того, чтобы избавиться от ШГ. Так что лучше сразу все делать в командной строке. На самом деле тут многие сходят с дистанции, стараясь как можно дольше делать все с помощью графических приложений, через какое-то время ломаются обновления, слетают драйвера и все катится в тартарары.

Этого нельзя допустить, поэтому сразу переходим к следующему этапу необходимости освоить консольный текстовый редактор: vim, emacs, или их клоны. Поверьте не стоит привязываться к простеньким nano, или mcedit, в которых даже undo еще не завезли. Освоив эти редакторы вы спокойно можете редактировать конфигурационные файлы в /etc, $HOME и получить надежный контроль над системой.

Кейс 2 настроить сетевое окружение в офисе


Сейчас с NetworkManager настраивать сети стало намного проще, а раньше для настройки беспроводного соединения обязательно нужно было редактировать файл wpa_supplicant.conf. Однако и сегодня функционал NetworkManager во многом пока еще ограничен. Например в нем нельзя подключиться к vpn по протоколу Juniper Pulse с двухфакторной аутентификацией только CLI.

|18:29:57|admin@srv:[~]> sudo openconnect --protocol=pulse \--authgroup ТOTP -u jsmith https://my.company-gateway.com

В офисе вполне может случится так, что заблокированы многие порты на исходящий и входящий трафик по главной сети, но разблокированы во второстепенной. Если подключится ко второстепенной сети, то тогда многие важные ресурсы главной сети становятся недоступны. С другой стороны утыкаться в межсетевой экран, тоже не хочется. Какой выход?

Берем важные подсети главной сети и прописываем их статистическим маршрутом.

sudo ip route add 110.10.0.0/8 via 110.10.10.1;

После чего подключаемся ко второстепенной сети и остальной трафик направляем через нее. Так как метрика главной сети меньше в абсолютном значении, у нее приоритет выше и при двух параллельных default route трафик все же упорно идет через нее. Так как поменять метрику маршрута нельзя, проще удалить ненужный default route.

sudo ip route delete default dev eth0;

Для одного раза достаточно запустить эти две команды, но каждый день так подключаться неудобно, надо это автоматизировать. Для этого нужно создать скрипт в папке /etc/NetworkManager/dispatcher.d/.

|17:43:17|admin@srv:[~]> ls /etc/NetworkManager/dispatcher.d/10-openrc-status no-wait.d pre-down.d pre-up.d|17:43:22|admin@srv:[~]> cd /etc/NetworkManager/dispatcher.d/pre-up.d|17:43:27|admin@srv:[~]> sudo chmod +x 10-office-netw.sh

Скрипты, которые следует выполнить перед активацией сетевого соединения должны находиться в pre-up.d. Соответственно, скрипты на случай отключения сетевого соединения следует класть в pre-down.d. Названия могут быть произвольные, если скриптов несколько, то они будут выполняться в алфавитном порядке.

#!/bin/bashif [ $1 == eth0 ] && [ $2 == up ]; thenip route add 110.10.0.0/8 via 110.10.10.1ip route delete default dev eth0#более высокая метрика, чтобы быть ниже основного gw в ip routeip route add default dev eth0 metric 700fi

Объективности ради надо сказать, что директивы ip route add можно реализовать из интерфейса NetworkManager в свойствах в закладке соединения IPv4 => Routes.

Кейс 3 поднять Wireguard VPN


Буквально каждый день мы получаем новые доказательство в пользу того, что неплохо бы обзавестись собственным VPN решением. Сегодня под запретом торренты, зарубежные букмекерские сайты, завтра решат ограничить социальные сети и онлайн-библиотеки, а затем и новостные ресурсы кому-то не понравятся. Благо технологии тоже не стоят на месте и при соответствующих навыках можно за 15 минут настроить Wireguard VPN и обходить все нелепые ограничения. Самое главное наличие Linux сервера с внешним, т е не российским IP адресом.

Вся настройка происходит исключительно с использование CLI и текстовых конфигурационных файлов. Это не полноценный гайд по настройке, так как весь процесс состоит из следующих этапов.

  1. Установить пакет утилит Wireguard.
    aptitude install wireguard-tools
  2. Установить kernel-headers для более ранних версий ядра.
    aptitude install linux-headers
  3. Открыть наружу связующий UDP порт (в нашем примере 51820) с управляющей консоли сервиса виртуального сервера.
  4. Создать открытый и закрытый ключи для Wireguard на клиенте и сервере.
    umask 077; wg genkey | tee privatekey | wg pubkey > publickey
  5. Создать конфигурационный файл в /etc/wireguard.
  6. Проверить наличие L2 соединения.
    wg show, если есть нечто вроде transfer: 4.80 MiB received, 1833.04 KiB sent, то это хороший признак.
  7. Подключить IP Forwarding с помощью sysctl -w net.ipv4.ip_forward=1 и прописать в /etc/sysctl.conf, если этого еще не было сделано.
  8. Настроить маршрутизацию трафика и NAT masquerade.

На самом деле в терминологии Wireguard участники сетевого соединения именуются не клиент и сервер, а peers равные. Однако на деле, все же один из узлов выполняет функцию именно сервера, поэтому логичнее их так и именовать для данного кейса.

Конфигурационный файл VPN-сервера содержит собственный закрытый ключ и открытый ключ клиента. Обратное также верно, в конфигурационном файле VPN-клиента прописываем собственный закрытый ключ и открытый ключ сервера.

#client config[Interface]PrivateKey = uJPzgCQ6WNlAUp3s5rabE/EVt1qYh3Ym01sx6oJI0V4Address = 192.168.10.2/24[Peer]PublicKey = qdjdqh2pN3DEMDUDRob8K3bp9BZFJbT59fprBrl99zMAllowedIPs = 0.0.0.0/0Endpoint = 172.105.211.120:51820PersistentKeepalive = 20

Любая неточность в каждом из перечисленных пунктов, кроме проверки OSI L2 соединения приведет к сбою в работе VPN туннеля, но при необходимой сноровке все можно сделать быстро и точно.

#server conifg[Interface]Address = 192.168.10.1/24ListenPort = 51820PrivateKey = eEvqkSJVw/7cGUEcJXmeHiNFDLBGOz8GpScshecvNHUSaveConfig = true[Peer]PublicKey = 2H8vRWKCrddLf8vPwwTLMfZcRhOj10UBdc0j8W7yQAk=AllowedIPs = 192.168.10.2/32

В некоторых примерах AllowedIPs клиента бывает выставлен на внутренний туннельный IP адрес сервера непонятно зачем. Тогда только запросы на этот IP адрес и будут разрешены, если же выставить 0.0.0.0/0 то весь трафик пойдет через Wireguard VPN. Также Endpoint клиента обязательно должен указывать на внешний IP адрес сервера.

Остается настроить NAT masquerade для того, чтобы Wireguard сервер мог осуществлять маршрутизацию трафика в NAT среде.

#IPv4[root@wgsrv ~]$ iptables -A FORWARD -i wg0 -j ACCEPT[root@wgsrv ~]$ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE#IPv6[root@wgsrv ~]$ ip6tables -A FORWARD -i wg0 -j ACCEPT[root@wgsrv ~]$ ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE#NAT[root@wgsrv ~]$ iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE

После чего надо сохранить правила в базе iptables, или netfilter. Также и сервис wg-quick, заведующий VPN туннелем Wireguard, необходимо добавить в автозагрузку.

[root@wgsrv ~]$ systemctl enable wg-quick@wg0[root@wgsrv ~]$ systemctl netfilter-persistent save[root@wgsrv ~]$ systemctl enable netfilter-persistent

Заключение


Надеюсь приведенных примеров достаточно для того, чтобы показать насколько необходимо умение работать с CLI и насколько проще выполнять основные действия по системному администрированию даже для домашней рабочей станции с Linux OS.



Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Git GUI моей мечты

27.09.2020 18:10:26 | Автор: admin

Я разработчик игр и мобильных приложений. Я написал немало кода на C++ и Swift. И, как и многие из вас, я пользуюсь системами контроля версий, в частности, гитом.

Гит имеет максимально функциональный command-line интерфейс и десятки если не сотни приложений для работы с ним локально при помощи графического интерфейса, которые умеют выполнять только часть функционала гита. Беда в том, что я пишу код уже 10 лет, но никак не нашел идеальный (подходящий для меня) git GUI клиент. Пример: недавно вышел Github Desktop. Я его использовал до тех пор, пока мне не понадобилось сделать checkout на конкретный коммит. И я испытал уже привычную боль от того, что данное приложение так делать не умеет. И вновь вернулся в терминал (с автодополнением для гита). И такие вещи есть в каждом GUI приложении для гита. Однако, я сюда пришел не критиковать их. Уверен, что вы и без меня имеете много претензий к этим приложениям. Я долго думал о том каким должно быть идеальное git GUI приложение. Это были мимолетные обрывки желаний, из которых трудно собрать что-то цельное. И совсем недавно эти обрывки мыслей у меня собрались в единую картину. Ниже я опишу это в формате ТЗ (технического задания) в максимально понятной форме.

Идеальный Git GUI клиент

Важно чтобы интерфейс не был суперусложнен. Если юзер откроет прилажку и увидит больше 20 кнопок, то идея отстой. Бльшая часть пользователей переключаясь на консоль для работы с гитом пишут команду git status чтобы узнать список файлов с измененным статусом. Потому наше приложение должно почти на весь экран показывать список файлов в виде иерархии, которые имеют измененный статус (примерно как проводник/finder). Туда будет входить все, что мы можем увидеть командой git status: измененные файлы, untracked файлы, добавленные и удаленные (возможно, какой-то статус я забыл). Каждый файл должен как и в консоли отображаться либо красным цветом, либо зеленым, что показывает его добавленность в коммит. На любой файл можно кликнуть правой кнопкой мыши или нажать на три точки в правой части строки чтобы вызвать контекстное меню. В контекстном меню можно добавить файл если он не добавлен (git add команда в терминале), сделать reset если он добавлен, удалить если он не находится в индексе (clean). Еще можно кликнуть на папку правой кнопкой и добавить всю папку (git add folder). Аналогично работает reset. Еще можно добавить в индекс все маленькой кнопкой в верхнем левом углу дерева файлов. Можно кликнуть по строчке с файлом чтобы открыть дифф по нему на весь экран.

Сверху окна есть отдельный заголовочный блок примерно как в Xcode с названием ветки и статусом того, что сейчас происходит (pulling, pushing, idle). Это все. То есть, окно по-умолчанию только показывает статус: нынешнюю ветку и статус файлов.

Когда пользователь намерен сделать какое-либо действие (git log - посмотреть историю, git branch - операция с ветками, git commit - осуществление коммита, git push - загрузка в remote, git pull - загрузка из remote, git remote - управление списком remote и т.д.) он должен зажать tab чтобы вызвать селектор действий (прям как селектор оружия в GTA 5).

Далее пользователь наводит указатель мыши на нужную команду и кликает на нее. После этого происходит вызов команды если это команда без аргументов (например, pull, push, fetch). Если нужно указать аргументы, то пользователь кликает правой кнопкой на название команды (например, push) и появляется соответствующее модальное диалоговое окно с вводом аргументов (цель remote и имя ветки, а также галочка force), которые указываются мышью или горячими клавишами. К этом моменту изначально зажатый tab можно уже отпустить. Если кликнуть за пределами модального окна или нажать esc, то модальное окно закроется и вызов команды будет отменен. Если же в этом модальном окне указать аргументы и нажать кнопку push, то начнется выполнение команды. Процесс выполнения команды будет отображен на верхней панели под названием ветки.

Еще одно важное преимущество терминала над остальными git GUI приложениями это возможность склеивать команды последовательно при помощи операторов && и ||. Например, я лично в работе часто использую такую последовательность для подлития в свою ветку той ветки, от которой моя ветка была срезана:

git checkout dev && git pull && git checkout - && git merge -

Данная последовательность выполняет 4 операции:

  1. переключается на ветку dev

  2. скачивает последние изменения ветки dev

  3. переключается обратно на ветку, на которой мы находились до ветки dev

  4. вливает ветку dev в нынешнюю ветку

Оператор && устроен таким образом, что если одна из команд остановится из-за ошибки, то выполнение всех остальных команд не начнется. Это суперудобно, но я не видел ни одного git GUI клиента, который бы умел склеивать команды в последовательность (если вы такой знаете, то укажите это в комментариях). И я придумал как в моем потенциальном абстрактном идеальном git GUI приложении моей мечты это будет выполнено.

Для того, чтобы указать команду и добавить после нее другую команду нужно сделать то же самое, что и для указания обычной команды, но помимо кнопки tab нужно еще зажать alt (или shift, тут нужно еще подумать). То есть, в привычном уже круговом селекторе мы выбираем команду checkout, указываем ветку dev, жмем ok в окошке, где мы указывали ветку. Однако из-за того, что в момент появления окошка помимо клавиши tab была зажата еще и клавиша alt, команда checkout не начинает выполняться сразу после нажатия ok, а добавится в список, который появляется в верхнем левом углу, а селектор продолжит отображаться в ожидании новой команды (tab держать уже не нужно - селектор остается открытым после указания первой команды с зажатым alt). Также указываем другие команды левой или правой кнопкой мыши как обычно - они пополняют список команд. Когда мы завершили этот список жмем еще раз tab (или esc если передумали), селектор закрывается, команды начинают выполняться, их статус виден на верхней панели статуса. Признаться честно, на эту идею меня подтолкнул смелая фича в игре Red Alert 2. Там можно зажать клавишу z и указать последовательность точек куда следует идти юниту. Я считаю, что это гениальная фича, которой очень не хватает в других популярных стратегиях.

Отдельная фича, которая сделает использование истории гита удобнее, это хэширование хэша (да, именно так) коммита в виде эмоджи. Объясню на примере. Когда я смотрю историю коммитов, я вижу список труднозапоминаемых длинных хэшей типа такого

3a962a4a5979b338e4bfce1333b3009529c0ad08

d8def5c1b13a83a1df8797fab3d34760596df692

cffcf32979526f2e2d26c06fe0b73666a7ca0e87

2757ed191106b51b729c5437334d9d19b0d081b8

0fef00c89e8d60e41a452ffc31a8300b551116be

5e4fe2033549b89ef86834e2fcba350fa5099443

886602d02ec39f853c9b708957f27c3fbb51e66c

250ce1cd7b0b6ec415898e0a26cda49a2d326bd3

4a4a5e0f6257bb6c179981c4fb69011be7bbe53d

В этом ничего нет плохого пока не нужно запомнить хэш и сверить его с другим хэшем. Даже сокращенный вариант запомнить не так легко. Потому у меня появилась идея сделать хэши коммитов более запоминаемыми - по каждому хэшу вычисляем хэш в виде эмоджи. Тогда это будет выглядеть примерно так.

3a962a4a5979b338e4bfce1333b3009529c0ad08

d8def5c1b13a83a1df8797fab3d34760596df692

cffcf32979526f2e2d26c06fe0b73666a7ca0e87

2757ed191106b51b729c5437334d9d19b0d081b8

0fef00c89e8d60e41a452ffc31a8300b551116be

5 5e4fe2033549b89ef86834e2fcba350fa5099443

886602d02ec39f853c9b708957f27c3fbb51e66c

250ce1cd7b0b6ec415898e0a26cda49a2d326bd3

4a4a5e0f6257bb6c179981c4fb69011be7bbe53d

Эмоджи намного лучше запоминаются человеком, чем безымянные шестнадцатеричные хэши. Если везде, где мы ни показывали хэши коммитов (история или статус pullа), показывать их эмоджи хэши, то пользователь лучше будет помнить на каком он коммите работает прям сейчас, и меньше будет ошибок когда он забыл влить другую ветку, запушить или запулить изменения. Вообще было бы здорово если бы эмоджи-хэш использовался везде: на github, bitbucket, teamcity. Это позволило бы проще сверять коммиты билдов с коммитами в истории.

Заключение

Это все. Буду рад любой критике и предложениям в комментариях. Отвечу на вопрос зачем я делюсь идеей тут, а просто не начну реализовывать это сам. Я сам занимаюсь разработкой игр, на данный момент работаю в компании Playtika, а в свободное время разрабатываю приложение для работы с SQLite https://sqliteman.dev. Я бы сам с радостью занялся разработкой данного git GUI клиента, но у меня нет времени. Я буду очень рад если бы кто-то другой это сделал, при этом мне совершенно не жалко если кто-то это сделает без упоминания источника идеи. Я уверен, что идея не стоит ничего в отличие от реализации. И буду очень счастлив если у меня будет git GUI приложение моей мечты.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru